*deleted post (it was a terrible code…)
I’m trying to create a generative illustration system based on connecting graphics placed on regular cells on a grid. This drawing i made can help understanding what i want to achieve:
Every cell type has 2 lists associated: one with its possible TOP connections and another with its possible LEFT connections.
When drawing the grid (using a nested for loop), we need to find a cell type that connects with the one on top as well as with the one on the left. My mental program breakdown would be something like:
-
Manually fill topConnections and leftConnections lists per each cell typology (topConnections_A, topConnections_B… leftConnections_A, leftConnections_B…), assigning numbers to each typology (A=0, B=1, C=2…)
-
Make a grid on the canvas using a nested for loop, and fill the first cell with a random cell type.
-
Append the cell type that has just been drawn on a selectedCellTypes intList. (Would it be better to do this on a 2D array?)
-
Per every new iteration of the nested for loop, check which cell types connect with the top cell and fill a commonTop list with the cell types that match. Also, check which cell types connect with the left cell and fill a commonLeft list with the cell types that match. I discovered this can be easily done with the hasValue() method of intLists.
-
Find common values between commonTop and commonLeft lists, and fill a commonAll list with the found values.
-
Randomly select a value from the commonAll list, and draw the cell type that corresponds to this selected value.
-
Clear commonTop, commonLeft and commonAll lists
-
Go back to 3.
I have the feeling that this is maybe too complicated, and that there might be simpler solutions. I’m an awful programmer, so it won’t be a surprise if the whole concept is wrong too…
Any idea or existing example facing something similar?
thanks!!
I don’t fully understand the logic behind connecting squares. Would you mind explaining?
A few things that could make your life easier;
Maybe use classes and ArrayLists. Check out pointers and logic behind them. You can also pass any number of arguments to a function using this method: so you don’t have to manually type them out. Do remember to check the size of the array a
. It can be anything >= 0, so sometimes, it can give less than the required amount of inputs
void setup() {
aaa(1,2,3,4.5,6); //you can give as many float type entries as you want.
}
void aaa(float... a) {
printArray(a);
}
Well that is as far as I can get without understanding the logic behind the way you connect squares. Right now, I only see a mess of letters, so yeah. Well I hope you found these helpful.
Answering the question in the original post, before you deleted it; Finding common items of Any number of arrays. Read bellow to see how I would automate this process.
void setup() {
//I am using ArrayLists because they are easily resizable and I am familiar with them.
ArrayList<Integer> repeated = new ArrayList<Integer>(), a1 = new ArrayList<Integer>(),a2 = new ArrayList<Integer>(),a3 = new ArrayList<Integer>();
a1 = fillArray(new int[]{1,2,4,6,8,10,12}); //I don't know any better ways of filling an array without too much code.
a2 = fillArray(new int[]{1,3,6,9,12,15});
a3 = fillArray(new int[]{6}); //mess with the contents inside of the {} to change the outcome
repeated = commonValues(a1,a2); //a1 can be recycled and used instead of 'repeated'
repeated = commonValues(repeated, a3); //compare every next array with the same logic.
printArray(repeated);
}
ArrayList<Integer> fillArray(int[] contents) {
ArrayList<Integer> output = new ArrayList<Integer>();
for(int i = 0; i < contents.length; i++) output.add(contents[i]); //looping through each element in contents[] and adding it
return output;
}
ArrayList<Integer> commonValues(ArrayList<Integer> a, ArrayList<Integer> b) {
ArrayList<Integer> output = new ArrayList<Integer>();
for(int i = 0; i < a.size(); i++) {
if(b.contains(a.get(i))) output.add(a.get(i)); //if B contains element I of A (a.get(i)), it is added to the output
}
return output;
}
This is a way to compare ArrayLists if they hold common items.
Now what you can do is to create and ArrayList ArrayList.
So it would come out as
ArrayList<ArrayList<Integer>> aaa = new ArrayList<ArrayList<Integer>>();
aaa.add( ArrayList );
//to access elements use
int a = aaa.get( <arrayList position> ).get( <item position> )
well this is the best I can come up with now and it is late. So I am probably overlooking something. Well it can be used as an inspiration. Good luck and have a nice day!
wow, thanks for your detailed answer and commented code!
I’m going to study it and learn how to use ArrayLists, it looks like it can help with my research better than intLists.
Maybe to clarify a little bit, i created an example image:
On the left, filling the grid with randomly selected cells.
On the right, filling the grid with cells that connect ones to others.
Let’s see if i can explain my previous drawing:
-
The letters are just “identifiers” of each cell type.
-
The cell of type “A” can connect on top with the cells of type “A”, “D”, or “F”, and connect on the left with the cells of type “B”,“D”,“E” or “F”
-
The cell of type “B” can connect on top with the cells of type “A”, “C”, “E” or “F”, and connect on the left with the cells of type “B”,“C”, “E” or “F”
-
and so on…
I hope it can be understood better now!!!
Thanks again for your help, i’ll let you know how i’m progressing!
Most probably I’ll be evolving slowly, this not being a job nor an assignment i just dig into this when i have spare time…
Hey, from what I read, your pseudo code seems correct.
Why don’t you try to implement it? Who cares if there is a better solution, this is yours and you’ll learn plenty by implementing it.
If you get stuck at some specific points come back to get some help and get unlocked =)
What would happen if you had this:
would it try to connect to the B bellow, or would that be too complicated and unnecessary since you only use two squares to generate?
I am trying to make my own version of this but it is difficult and requires a lot of thinking. Thank you for the idea!
My goal for this project is to generate a and L shape of random cells and create the entire grid out of it.
cool! glad to inspire you CodeMasterX!
it’s difficult to tell the results without taking a pencil and drawing on the notebook (or implement the code), but i’m sure you can get very nice minimal drawings with just using 2 cells… i’ll be happy to see what you come up with!
on my side, i managed to implement the code and make it work (yeeeeha!),
and results are more or less as i expected.
It’s been a real pain to think on how to translate the idea into code, and although my implementation is too tough and unefficient, at least it works as a proof of concept by now:
int gridsize=25;
IntList leftAconnect;
IntList leftBconnect;
IntList leftCconnect;
IntList leftDconnect;
IntList leftEconnect;
IntList leftFconnect;
IntList topAconnect;
IntList topBconnect;
IntList topCconnect;
IntList topDconnect;
IntList topEconnect;
IntList topFconnect;
IntList toplist;
IntList leftlist;
IntList common;
IntList savedCells;
void setup(){
leftAconnect = new IntList();
leftBconnect = new IntList();
leftCconnect = new IntList();
leftDconnect = new IntList();
leftEconnect = new IntList();
leftFconnect = new IntList();
topAconnect = new IntList();
topBconnect = new IntList();
topCconnect = new IntList();
topDconnect = new IntList();
topEconnect = new IntList();
topFconnect = new IntList();
toplist=new IntList();
leftlist=new IntList();
//list of connectors
leftAconnect.append(new int[] {99}); //connecting with A on the left (99 means none)
leftBconnect.append(new int[] {0,1,4}); //connecting with B on the left
leftCconnect.append(new int[] {1,3,4,5}); //connecting with C on the left
leftDconnect.append(new int[] {0,3,5}); //connecting with D on the left
leftEconnect.append(new int[] {0,1,3,5}); //connecting with E on the left
leftFconnect.append(new int[] {0,1,4}); //connecting with F on the left
topAconnect.append(new int[] {0,1,4}); //connecting with A on top
topBconnect.append(new int[] {99}); //connecting with B on top
topCconnect.append(new int[] {1,2,5}); //connecting with C on top
topDconnect.append(new int[] {0,2,4,5}); //connecting with D on top
topEconnect.append(new int[] {1,2,5}); //connecting with E on top
topFconnect.append(new int[] {0,1,4}); //connecting with F on top
common = new IntList(); //list to save common left and top connectors
savedCells = new IntList(); //list to save thee actual configuration of cells on the grid
size(1000,1000);
frameRate(1);
}
void draw(){
background(255);
strokeWeight(2);
stroke(0);
int numcellsx=width/gridsize;
int numcellsy=height/gridsize;
int chosencell=0;
for(int j=0;j<numcellsy;j++){
for(int i=0;i<numcellsx;i++){
int index=numcellsx*j+i; //2d to 1D index
int leftindex=index-1;
int topindex=index-numcellsy;
int topcell=0;
int leftcell=0;
if(i==0&&j==0){ //first cell
int dice=int(random(5))+1; //first cell is a random type one
switch(dice){
case 0:
a(i*gridsize,j*gridsize,gridsize);
savedCells.append(0);
break;
case 1:
b(i*gridsize,j*gridsize,gridsize);
savedCells.append(1);
break;
case 2:
c(i*gridsize,j*gridsize,gridsize);
savedCells.append(2);
break;
case 3:
d(i*gridsize,j*gridsize,gridsize);
savedCells.append(3);
break;
case 4:
e(i*gridsize,j*gridsize,gridsize);
savedCells.append(4);
break;
case 5:
f(i*gridsize,j*gridsize,gridsize);
savedCells.append(5);
break;
}
}
if(j==0&&i!=0){ //first row (excluding first cell)
leftcell=savedCells.get(leftindex); //examine cell at the left
switch(leftcell){ //let's see what cell types can connect with the cell that we have to the left
case 0:
chosencell=int(random(leftAconnect.size())); //A type cell doesnt have any possible conections from the left, so it's possible that the first row becomes mainly empty
chosencell=leftAconnect.get(chosencell); //this needs to be solved with a better design of the cells
break;
case 1:
chosencell=int(random(leftBconnect.size()));
chosencell=leftBconnect.get(chosencell);
break;
case 2:
chosencell=int(random(leftCconnect.size()));
chosencell=leftCconnect.get(chosencell);
break;
case 3:
chosencell=int(random(leftDconnect.size()));
chosencell=leftDconnect.get(chosencell);
break;
case 4:
chosencell=int(random(leftEconnect.size()));
chosencell=leftEconnect.get(chosencell);
break;
case 5:
chosencell=int(random(leftFconnect.size()));
chosencell=leftFconnect.get(chosencell);
break;
}
}
if(i==0&&j!=0){ //on the first column (excluding first cell)
topcell=savedCells.get(topindex); //examine cell on top
switch(topcell){ //let's see what cell types can connect with the cell that we have on top
case 0:
chosencell=int(random(topAconnect.size()));
chosencell=topAconnect.get(chosencell);
break;
case 1:
chosencell=int(random(topBconnect.size()));
chosencell=topBconnect.get(chosencell);
break;
case 2:
chosencell=int(random(topCconnect.size()));
chosencell=topCconnect.get(chosencell);
break;
case 3:
chosencell=int(random(topDconnect.size()));
chosencell=topDconnect.get(chosencell);
break;
case 4:
chosencell=int(random(topEconnect.size()));
chosencell=topEconnect.get(chosencell);
break;
case 5:
chosencell=int(random(topFconnect.size()));
chosencell=topFconnect.get(chosencell);
break;
}
}
if(i!=0&&j!=0){ //on any other position
topcell=savedCells.get(topindex); //lets see what cell we have on top
leftcell=savedCells.get(leftindex); //and what cell we have to the left
switch(topcell){ ////let's see what cell types can connect with the cell that we have on top
case 0:
for(int k=0;k<topAconnect.size();k++){
toplist.append(topAconnect.get(k)); //fill toplist with the possible connectors of the cell on top
}
break;
case 1:
for(int k=0;k<topBconnect.size();k++){
toplist.append(topBconnect.get(k));
}
break;
case 2:
for(int k=0;k<topCconnect.size();k++){
toplist.append(topCconnect.get(k));
}
break;
case 3:
for(int k=0;k<topDconnect.size();k++){
toplist.append(topDconnect.get(k));
}
break;
case 4:
for(int k=0;k<topEconnect.size();k++){
toplist.append(topEconnect.get(k));
}
break;
case 5:
for(int k=0;k<topFconnect.size();k++){
toplist.append(topFconnect.get(k));
}
break;
}
switch(leftcell){ //let's see what cell types can connect with the cell that we have to the left
case 0:
for(int k=0;k<leftAconnect.size();k++){
leftlist.append(leftAconnect.get(k)); //fill leftlist with the possible connectors of the cell on the left
}
break;
case 1:
for(int k=0;k<leftBconnect.size();k++){
leftlist.append(leftBconnect.get(k));
}
break;
case 2:
for(int k=0;k<leftCconnect.size();k++){
leftlist.append(leftCconnect.get(k));
}
break;
case 3:
for(int k=0;k<leftDconnect.size();k++){
leftlist.append(leftDconnect.get(k));
}
break;
case 4:
for(int k=0;k<leftEconnect.size();k++){
leftlist.append(leftEconnect.get(k));
}
break;
case 5:
for(int k=0;k<leftFconnect.size();k++){
leftlist.append(leftFconnect.get(k));
}
break;
}
for(int m=0;m<6;m++){
if(leftlist.hasValue(m)&&toplist.hasValue(m)){
common.append(m); //fill the common list with matching connectors on left and top list
}
else{
common.append(int(random(6))); //if there's no matches, choose a random cell
/*
//this solution below might have been better than choosing a random cell when there's no common matches,
//but it is not working for some reason i'm too tired to understand
if(leftlist.size()>0){
common.append(leftlist.get(int(random(leftlist.size())))); //if leftlist has something, choose a random option
}
if(toplist.size()>0){
common.append(toplist.get(int(random(toplist.size())))); //if toplist has sometthing, choose a random option
}
if(leftlist.size()==0&&toplist.size()==0){
common.append(int(random(6)));
}
*/
}
}
int ran=int(random(common.size()));
chosencell=common.get(ran); //choose a random cell from the common list
}
//draw chosen cell
switch(chosencell){
case 0:
a(i*gridsize,j*gridsize,gridsize);
savedCells.append(0); //save the drawn cell ideentifyer into the savedCells list
break;
case 1:
b(i*gridsize,j*gridsize,gridsize);
savedCells.append(1);
break;
case 2:
c(i*gridsize,j*gridsize,gridsize);
savedCells.append(2);
break;
case 3:
d(i*gridsize,j*gridsize,gridsize);
savedCells.append(3);
break;
case 4:
e(i*gridsize,j*gridsize,gridsize);
savedCells.append(4);
break;
case 5:
f(i*gridsize,j*gridsize,gridsize);
savedCells.append(5);
break;
case 99: //blank
savedCells.append(99);
break;
}
leftlist.clear(); //clear lists
toplist.clear();
common.clear();
}
}
savedCells.clear(); //clear the whole grid list before starting again
}
void a(int x, int y, int sz){
line(x,y,x,y+sz);
}
void b(int x, int y, int sz){
line(x,y,x+sz,y);
}
void c(int x, int y, int sz){
line(x+sz,y,x+sz,y+sz);
}
void d(int x, int y, int sz){
line(x,y+sz,x+sz,y+sz);
}
void e(int x, int y, int sz){
line(x,y,x+sz,y+sz);
}
void f(int x, int y, int sz){
line(x,y+sz,x+sz,y);
}
I guess this might be improved by using 2D arrays and classes, both of which i never had the bravery to dig into… If someone understands this code and comes up with a clear path to follow for synthesizing to a briefer, more efficient code, i’d be pleased to hear about it!
Going to bed now, dreaming on what cool drawings to be done with this!
oops! several errors on the code were dropping incorrect results. My prior results were just an illusion! this is the real thing, for this set of cells:
for a different set of 4 cells:
it puzzles me how “blocky” the results are in this second one. Maybe related to random number generation?
In any case, the code without the previous errors:
int gridsize=10;
IntList leftAconnect;
IntList leftBconnect;
IntList leftCconnect;
IntList leftDconnect;
IntList leftEconnect;
IntList leftFconnect;
IntList topAconnect;
IntList topBconnect;
IntList topCconnect;
IntList topDconnect;
IntList topEconnect;
IntList topFconnect;
IntList toplist;
IntList leftlist;
IntList common;
IntList savedCells;
void setup(){
leftAconnect = new IntList();
leftBconnect = new IntList();
leftCconnect = new IntList();
leftDconnect = new IntList();
leftEconnect = new IntList();
leftFconnect = new IntList();
topAconnect = new IntList();
topBconnect = new IntList();
topCconnect = new IntList();
topDconnect = new IntList();
topEconnect = new IntList();
topFconnect = new IntList();
toplist=new IntList();
leftlist=new IntList();
//list of connectors
leftAconnect.append(new int[] {99}); //connecting with A on the left (99 means none)
leftBconnect.append(new int[] {0,1,4}); //connecting with B on the left
leftCconnect.append(new int[] {1,3,4,5}); //connecting with C on the left
leftDconnect.append(new int[] {0,3,5}); //connecting with D on the left
leftEconnect.append(new int[] {0,1,3,5}); //connecting with E on the left
leftFconnect.append(new int[] {0,1,4}); //connecting with F on the left
topAconnect.append(new int[] {0,1,4}); //connecting with A on top
topBconnect.append(new int[] {99}); //connecting with B on top
topCconnect.append(new int[] {1,2,5}); //connecting with C on top
topDconnect.append(new int[] {0,2,4,5}); //connecting with D on top
topEconnect.append(new int[] {1,2,5}); //connecting with E on top
topFconnect.append(new int[] {0,1,4}); //connecting with F on top
common = new IntList(); //list to save common left and top connectors
savedCells = new IntList(); //list to save thee actual configuration of cells on the grid
size(900,800);
frameRate(1);
}
void draw(){
background(255);
strokeWeight(3);
stroke(0);
int numcellsx=width/gridsize;
int numcellsy=height/gridsize;
int chosencell=0;
for(int j=0;j<numcellsy;j++){
for(int i=0;i<numcellsx;i++){
int index=numcellsx*j+i; //2d to 1D index
int leftindex=index-1;
int topindex=index-numcellsx;
int topcell=0;
int leftcell=0;
if(index==0){ //first cell
int dice=int(random(6)); //first cell is a random type one
chosencell=dice;
}
if(j==0&&i!=0){ //first row (excluding first cell)
leftcell=savedCells.get(leftindex); //examine cell at the left
switch(leftcell){ //let's see what cell types can connect with the cell that we have to the left
case 0:
chosencell=int(random(leftAconnect.size()));
chosencell=leftAconnect.get(chosencell);
break;
case 1:
chosencell=int(random(leftBconnect.size()));
chosencell=leftBconnect.get(chosencell);
break;
case 2:
chosencell=int(random(leftCconnect.size()));
chosencell=leftCconnect.get(chosencell);
break;
case 3:
chosencell=int(random(leftDconnect.size()));
chosencell=leftDconnect.get(chosencell);
break;
case 4:
chosencell=int(random(leftEconnect.size()));
chosencell=leftEconnect.get(chosencell);
break;
case 5:
chosencell=int(random(leftFconnect.size()));
chosencell=leftFconnect.get(chosencell);
break;
}
}
if(i==0&&j!=0){ //on the first column (excluding first cell)
topcell=savedCells.get(topindex); //examine cell on top
switch(topcell){ //let's see what cell types can connect with the cell that we have on top
case 0:
chosencell=int(random(topAconnect.size()));
chosencell=topAconnect.get(chosencell);
break;
case 1:
chosencell=int(random(topBconnect.size()));
chosencell=topBconnect.get(chosencell);
break;
case 2:
chosencell=int(random(topCconnect.size()));
chosencell=topCconnect.get(chosencell);
break;
case 3:
chosencell=int(random(topDconnect.size()));
chosencell=topDconnect.get(chosencell);
break;
case 4:
chosencell=int(random(topEconnect.size()));
chosencell=topEconnect.get(chosencell);
break;
case 5:
chosencell=int(random(topFconnect.size()));
chosencell=topFconnect.get(chosencell);
break;
}
}
if(i!=0&&j!=0){ //on any other position
topcell=savedCells.get(topindex); //lets see what cell we have on top
leftcell=savedCells.get(leftindex); //and what cell we have to the left
switch(topcell){ ////let's see what cell types can connect with the cell that we have on top
case 0:
for(int k=0;k<topAconnect.size();k++){
toplist.append(topAconnect.get(k)); //fill toplist with the possible connectors of the cell on top
}
break;
case 1:
for(int k=0;k<topBconnect.size();k++){
toplist.append(topBconnect.get(k));
}
break;
case 2:
for(int k=0;k<topCconnect.size();k++){
toplist.append(topCconnect.get(k));
}
break;
case 3:
for(int k=0;k<topDconnect.size();k++){
toplist.append(topDconnect.get(k));
}
break;
case 4:
for(int k=0;k<topEconnect.size();k++){
toplist.append(topEconnect.get(k));
}
break;
case 5:
for(int k=0;k<topFconnect.size();k++){
toplist.append(topFconnect.get(k));
}
break;
}
switch(leftcell){ //let's see what cell types can connect with the cell that we have to the left
case 0:
for(int k=0;k<leftAconnect.size();k++){
leftlist.append(leftAconnect.get(k)); //fill leftlist with the possible connectors of the cell on the left
}
break;
case 1:
for(int k=0;k<leftBconnect.size();k++){
leftlist.append(leftBconnect.get(k));
}
break;
case 2:
for(int k=0;k<leftCconnect.size();k++){
leftlist.append(leftCconnect.get(k));
}
break;
case 3:
for(int k=0;k<leftDconnect.size();k++){
leftlist.append(leftDconnect.get(k));
}
break;
case 4:
for(int k=0;k<leftEconnect.size();k++){
leftlist.append(leftEconnect.get(k));
}
break;
case 5:
for(int k=0;k<leftFconnect.size();k++){
leftlist.append(leftFconnect.get(k));
}
break;
}
boolean hascommon=false;
//check if there's comoon values
for(int m=0;m<6;m++){
if(leftlist.hasValue(m)&&toplist.hasValue(m)){
hascommon=true;
common.append(m); //fill the common list with matching connectors on left and top list
}
}
//if there's no common values, get a random one from the left or top lists
if(hascommon==false){
if(leftlist.size()>0){
common.append(leftlist.get(int(random(leftlist.size()))));
}
if(toplist.size()>0){
common.append(toplist.get(int(random(toplist.size()))));
}
}
if(hascommon==false&&common.size()>0){
int ran=int(random(common.size()));
chosencell=common.get(ran); //choose a random cell from the common list
}
//if there's no possible connectors, get a random or blank cell
if(hascommon==false&&common.size()<=0){
if (random(100)<50){
chosencell=int(random(6));
}else{
chosencell=99;
}
}
}
//draw chosen cell
switch(chosencell){
case 0:
a(i*gridsize,j*gridsize,gridsize);
savedCells.append(0); //save the drawn cell ideentifyer into the savedCells list
break;
case 1:
b(i*gridsize,j*gridsize,gridsize);
savedCells.append(1);
break;
case 2:
c(i*gridsize,j*gridsize,gridsize);
savedCells.append(2);
break;
case 3:
d(i*gridsize,j*gridsize,gridsize);
savedCells.append(3);
break;
case 4:
e(i*gridsize,j*gridsize,gridsize);
savedCells.append(4);
break;
case 5:
f(i*gridsize,j*gridsize,gridsize);
savedCells.append(5);
break;
case 99: //blank
savedCells.append(99);
break;
}
leftlist.clear(); //clear lists
toplist.clear();
common.clear();
}
}
savedCells.clear(); //clear the whole grid list before starting again
}
void a(int x, int y, int sz){
line(x,y,x,y+sz);
}
void b(int x, int y, int sz){
line(x,y,x+sz,y);
}
void c(int x, int y, int sz){
line(x+sz,y,x+sz,y+sz);
}
void d(int x, int y, int sz){
line(x,y+sz,x+sz,y+sz);
}
void e(int x, int y, int sz){
line(x,y,x+sz,y+sz);
}
void f(int x, int y, int sz){
line(x,y+sz,x+sz,y);
}
Hi again @eltrem,
Now that you have come with your solution here’s my version if it is any interest for you.
Everything is detailed in the code but I want to point out a few things that may be of interests for you:
First, this version is using OOP concepts and I advise you to start digging this as it will ease your life later down the road.
Second, I’m dealing with the creation and drawing of the cells with bitwise logic. The idea is to use the bits of an integer to encode the design of a Cell.
For this, I defined some rules like the first bit encode the left side of the cell (it is 1 if I need to draw it and 0 if not). The second bit for the top, the third for the right and so on…
So, for exemple if I have the binary number 00000010, I know that since the second bit is 1 I will have to draw the top of the cell.
To ease my life even more, I used an enumeration to give a name to each basic design:
LEFT = 00000001
TOP = 00000010
RIGHT = 00000100
BOT = 00001000
...
When creating a cell I can then use bitwise OR operations to combine the different design. Let’s say I want a cell with the left and bottom side drawn. I would simply do (LEFT | BOT) which will give me the binary number 00001001
.
When I need to draw the cell, I can now used what’s called bitmasking (using the bitwise AND oparator) to know if I need to draw an edge or not. If I take the same binary number as previously, we have:
00001001 & LEFT = 00000001
00001001 & TOP = 00000000
00001001 & RIGHT = 00000000
00001001 & BOT = 00001000
As you can see, only the sides I included are none 0. You can use this fact to check weither or not you need to draw something.
Finally, when generating the grid, instead of keeping track of all the possible cells that could fit in a given position, I simply shuffle an array of all the existing cells instead. I can then check them one by one and I put the first one that fits. That’s a nice way of randomizing the grid without complicating the logic.
Please find the code below and don’t hesitate to ask questions if there are still things you don’t get:
Grid g;
Cell[] m_cellTypes;
void setup() {
size(800, 600);
background(20);
noSmooth();
g = new Grid(80, 60, 8);
g.generate();
}
void draw() {
background(20);
g.draw();
}
void mousePressed() {
g.generate();
}
/**
* CellWall is an enumeration that enable easy creation of the "design" of a cell
* using bitwise OR operator and easy storing/decoding using bitmasking and bitwise AND operator
*/
public enum CellWall {
LEFT (1 << 0),
TOP (1 << 1),
RIGHT (1 << 2),
BOT (1 << 3),
DIAG1 (1 << 4),
DIAG2 (1 << 5);
private final int m_cellWall;
private CellWall(int l_cellWall) {
m_cellWall = l_cellWall;
}
public int toInt() {
return m_cellWall;
}
}
/**
* Cell is the class used to define different type of cell
* It is defined by a given design: which of the 4 sides and 2 diagonals to draw
* as well as by a set of rules indicating which other set of cells can be on its left and top
*
* /!\ A cell object does not have an ID to be identified. Instead each Cell should be stored in an array
* and its position in the array become its identifier. That's why the cells that are allowed on its left and top are
* defined with an integer.
*/
class Cell {
private int[] m_leftOptions, m_topOptions; // List of Cell authorised on the left and on the top of this one
private int m_design; // Which wall of the cell to draw
/**
* Basic constructor simply initializing all our member variables
*/
public Cell(int l_design, int[] l_leftOptions, int[] l_topOptions) {
m_design = l_design;
m_leftOptions = l_leftOptions;
m_topOptions = l_topOptions;
}
/**
* Given the cell on the left and the cell on the top, check if this on could fit
* The left cell should be in the m_leftOptions array AND the right cell in the m_topOptions array
*
* @PARAM l_left the cell "id" that would be on the left of this one
* @PARAM l_top the cell "id" that would be on the top of this one
* @RETURN true if this cell check the connection rules, false otherwise
*/
public boolean isValid(int l_left, int l_top) {
boolean fitLeft = false;
for (int i = 0; i < m_leftOptions.length; i++) {
if (m_leftOptions[i] == l_left) {
fitLeft = true;
break;
}
}
boolean fitTop = false;
for (int i = 0; i < m_topOptions.length; i++) {
if (m_topOptions[i] == l_top) {
fitTop = true;
break;
}
}
return (fitLeft && fitTop);
}
/**
* Draw the cell on the screen given its top left corner position and its size
* It uses bitmask to define which "Wall" to draw
*
* @PARAM l_x x-coordinate of the top left corner
* @PARAM l_y y-coordinate ot the top left corner
* @PARAM l_size width and height of the cell
*/
public void draw(float l_x, float l_y, float l_size) {
noFill();
stroke(230);
strokeWeight(2);
if ((m_design & CellWall.LEFT.toInt()) != 0) line(l_x , l_y , l_x , l_y + l_size);
if ((m_design & CellWall.TOP.toInt()) != 0) line(l_x , l_y , l_x + l_size, l_y );
if ((m_design & CellWall.RIGHT.toInt()) != 0) line(l_x + l_size, l_y , l_x + l_size, l_y + l_size);
if ((m_design & CellWall.BOT.toInt()) != 0) line(l_x , l_y + l_size, l_x + l_size, l_y + l_size);
if ((m_design & CellWall.DIAG1.toInt()) != 0) line(l_x , l_y , l_x + l_size, l_y + l_size);
if ((m_design & CellWall.DIAG2.toInt()) != 0) line(l_x + l_size, l_y , l_x , l_y + l_size);
}
}
/**
* The gride class takes care of generating a set of Cells in a grid pattern
* based on the connection rules defined for those Cells.
* It also center the result on the canvas
*/
class Grid {
private int m_cellSize; // The width and height of a cell
private PVector m_offset; // The offset of the top left corner of the grid si it is centered
private int m_xCellNb, m_yCellNb; // The horizontal and vertical number of cells to generate
private Cell[] m_cellTypes; // The type of Cells that can be used
private int[][] m_cells; // The grid of Cells
/**
* Constructor
*/
public Grid(int l_xCellNb, int l_yCellNb, int l_cellSize) {
m_cellSize = l_cellSize;
m_xCellNb = l_xCellNb;
m_yCellNb = l_yCellNb;
m_cells = new int[m_xCellNb][m_yCellNb];
computeOffset();
generateCellTypes();
}
/**
* Compute were the top left corner of the grid should be on the screen so the grid is centered
*/
private void computeOffset() {
m_offset = new PVector();
int xGridSize = m_xCellNb * m_cellSize;
int yGridSize = m_yCellNb * m_cellSize;
m_offset.x = 0.5 * (width - xGridSize);
m_offset.y = 0.5 * (height - yGridSize);
}
/**
* This function is used to define the different Cells that could be used:
* Their design as well as their connection rules
*
* /!\ -1 is added to all the possible connections to easily handle edge cases for the Cells
* at the complete left or at the complete top.
* Because of this, instead of rewrirring the Cell.isValid(int l_left, int l_top) function for the cases were
* there is only the left Cell to check (on the first row) or only the top Cell to check (on the first column)
* Indeed for those case, l_left and l_top can be replaced by -1 in both situation respectively ensuring that this
* Cell will always find a match on the left or on the top.
*/
private void generateCellTypes() {
m_cellTypes = new Cell[7];
m_cellTypes[0] = new Cell(CellWall.LEFT.toInt(), new int[] {-1, 1, 3, 4, 5}, new int[] {-1, 0, 3, 5});
m_cellTypes[1] = new Cell(CellWall.TOP.toInt(), new int[] {-1, 1, 2, 4, 5}, new int[] {-1, 0, 2, 4, 5});
m_cellTypes[2] = new Cell(CellWall.RIGHT.toInt(), new int[] {-1, 0, 1, 2, 3, 4, 5}, new int[] {-1, 2, 3, 4});
m_cellTypes[3] = new Cell(CellWall.BOT.toInt(), new int[] {-1, 2, 3, 4}, new int[] {-1, 0, 1, 2, 3, 4, 5});
m_cellTypes[4] = new Cell(CellWall.DIAG1.toInt(), new int[] {-1, 1, 2, 5}, new int[] {-1, 0, 3, 5});
m_cellTypes[5] = new Cell(CellWall.DIAG2.toInt(), new int[] {-1, 2, 3, 4}, new int[] {-1, 2, 3, 4});
m_cellTypes[6] = new Cell(CellWall.DIAG1.toInt() | CellWall.DIAG2.toInt(), new int[] {-1, 1, 2, 5}, new int[] {-1, 2, 3, 4});
}
/**
* This function is the brain of this class.
* This the one responsible for generating the grid by respecting the set of connection
* rules defined previously
*/
public void generate() {
// Generate a pool array to pick random cellType from
// The pool array will contain a unique list of all the possible Cell to chose from
// Then, before chosing which Cell to put at one grid location, that pool array will be shuffled
// Each Cell of that pool array will then be check to be sure it can fit at that grid location
// Without breaking any connection rules. If it is not possible, the next one is checked and so on.
// This is a way of randomizing the grid generation if 2 or more Cell can fit in the same grid position.
int[] pool = new int[m_cellTypes.length];
for (int k = 0; k < pool.length; k++) {
pool[k] = k;
}
// Pick first cell at random
m_cells[0][0] = (int)random(m_cellTypes.length);
// Pick first row
for (int i = 1; i < m_xCellNb; i++) {
shuffle(pool);
for (int k = 0; k < pool.length; k++) {
if (m_cellTypes[pool[k]].isValid(m_cells[i - 1][0], -1)) {
m_cells[i][0] = pool[k];
break;
}
}
}
// Pick first col
for (int j = 1; j < m_yCellNb; j++) {
shuffle(pool);
for (int k = 0; k < pool.length; k++) {
if (m_cellTypes[pool[k]].isValid(-1, m_cells[0][j - 1])) {
m_cells[0][j] = pool[k];
break;
}
}
}
// Pick the rest of the grid
for (int i = 1; i < m_xCellNb; i++) {
for (int j = 1; j < m_yCellNb; j++) {
shuffle(pool);
for (int k = 0; k < pool.length; k++) {
if (m_cellTypes[pool[k]].isValid(m_cells[i - 1][j], m_cells[i][j - 1])) {
m_cells[i][j] = pool[k];
break;
}
}
}
}
}
/**
* Draw the result of the grid generation
*/
public void draw() {
for (int i = 0; i < m_xCellNb; i++) {
for (int j = 0; j < m_yCellNb; j++) {
m_cellTypes[m_cells[i][j]].draw(m_offset.x + i * m_cellSize, m_offset.y + j * m_cellSize, m_cellSize);
}
}
}
/**
* Shuffle an array randomly
*
* @PARAM l_arr The array to be shuffled
*/
private void shuffle(int[] l_arr) {
for (int i = l_arr.length - 1; i > 0; i--)
{
int index = (int)random(i + 1);
// Simple swap
int a = l_arr[index];
l_arr[index] = l_arr[i];
l_arr[i] = a;
}
}
}
OMG!
thank you very much @jb4x!!!
To be honest, most of this ninja code is out of my understanding, but i can see some of the logic behind it… great homework for christmas holidays!
I see i need to learn about OOP for clarity and scalability (my last project was around 9000 lines of code without a single class and it is a nightmare to edit: FORMS – String Quartet |)… and the holy mystery of bitwise operations too… so thank you very much for the commented code and the specific examples, it’s going to help a lot on the learning process!!!
i see in your program the array is being shuffled at mouse clicks… but i understand this is breaking the connection rules, right? visually it appears to me less structured than the first iteration (although maybe more “beautiful”… but who cares about beauty?)
Thanks for taking your time to work on this!!!
i really hope that you can use this code further than just for helping annoying humanoids on the internet…
I edited my code above, I had a mistake in the Cell constructor:
//It was:
public Cell(int l_design,int[] l_topOptions, int[] l_leftOptions)
//It should be:
public Cell(int l_design, int[] l_leftOptions, int[] l_topOptions)
When clicking the mouse, it simply ask the grid to generate a new pattern. Since the top left Cell is taken at random and, as you showed in one of your post above, sometimes several solutions are possible, and that those too are taken at random, the end result will changed.
BUT the connection rules are never broken.
The thing is that with this code, I’m not checking if a solution exists or not. Meaning that if at some point in the grid, no cell fits the connection rules then nothing happen so the grid keep the same value as it was before.
Since at the beginning, the grid is set to 0 everywhere, when no solution is found, it stays at 0 (which correspond to your A cell in your example above). So when you see those long vertical lines its mainly because no cells can fit there so it stays the same.
Then every time you click the mouse, you generate a new grid, and because it is random, maybe this time instead of being 0 it will become 2 and so the vertical lines will slowly disappear while other parts of the grid will remain the same.
I’m not sure I manage to explain it quite right but I hope you get the general idea.
Now this is said, I think it does not make any sense to have situation where no cell can connect because if that’s the case what do you do next? Ok you can draw nothing but then what’s the connection rules with empty cell? If then any cells can connect to it then you just need to create a new cell type completely blank with those connection rules.
Of course the result will be a bit different since you may found empty cells even if other cells could have fit but I don’t really see it as an issue. It is up to you to decide after.
All that to say that I coded an additional helper function that you can add to the grid class that check is there is always a solution that exists for all the possible configurations:
/**
* Check if the is always a possible solution given the connection rule set
*
* @RETURN True if the rule set will always provide a solution, false otherwise
*/
public boolean isRuleSetValid() {
boolean solutionFound;
for (int i = 0; i < m_cellTypes.length; i++) { // left
for (int j = 0; j < m_cellTypes.length; j++) { // top
solutionFound = false;
for (int k = 0; k < m_cellTypes.length; k++) { // middle
if (m_cellTypes[k].isValid(i, j)) {
solutionFound = true;
break;
}
}
if (!solutionFound) return false;
}
}
return true;
}
With the following rule set:
private void generateCellTypes() {
m_cellTypes = new Cell[7];
m_cellTypes[0] = new Cell(CellWall.LEFT.toInt(), new int[] {-1, 1, 3, 4, 5, 6}, new int[] {-1, 0, 3, 5, 6});
m_cellTypes[1] = new Cell(CellWall.TOP.toInt(), new int[] {-1, 1, 2, 4, 5, 6}, new int[] {-1, 0, 2, 4, 5, 6});
m_cellTypes[2] = new Cell(CellWall.RIGHT.toInt(), new int[] {-1, 0, 1, 2, 3, 4, 5, 6}, new int[] {-1, 2, 3, 4, 6});
m_cellTypes[3] = new Cell(CellWall.BOT.toInt(), new int[] {-1, 2, 3, 4, 6}, new int[] {-1, 0, 1, 2, 3, 4, 5, 6});
m_cellTypes[4] = new Cell(CellWall.DIAG1.toInt(), new int[] {-1, 1, 2, 5, 6}, new int[] {-1, 0, 3, 5, 6});
m_cellTypes[5] = new Cell(CellWall.DIAG2.toInt(), new int[] {-1, 2, 3, 4, 6}, new int[] {-1, 2, 3, 4, 6});
m_cellTypes[6] = new Cell(0, new int[] {-1, 0, 1, 2, 3, 4, 5, 6}, new int[] {-1, 0, 1, 2, 3, 4, 5, 6});
}
I get results like this:
Did you make the lines on the screen of the musical video?
How did you do it?
Did you use overlapping images and whenever using the white image, played a sound? Height would translate to pitch and brightness to volume?
With this, you could dump all of the remaining to the one who designed the images. All you would have to do, would be to display all images with a certain transparency, stack them and read the N-th coloumb of every image. When using the white color, just play a sound.
But I am probably discovering hot water. You probably did something similar. It sounds like an interesting project.
Thanks for keeping on evolving this @jb4x!
I love the results on your last implementation with the new rule set. That’s pretty much what my drawings on the notebook where looking like when first thinking about the algorithm!
On your last thoughts regarding non-solvable situations, i find the chosen rule is certainly vital; it is really interesting how, on this situation, going for a random cell or a blank cell drops totally different results. On my experiments, going for a blank cell (which couldn’t also connect with any other cell) threw this descending diagonal structures -which somehow reveal the way the for loop is iterating from top left to bottom right-.
I’ll be definitely digging deep during christmas with all this… and hopefully coming with some nice plots for family presents for king’s day!
I see this system as a sort of “smart truchet”, and i’m really eager to explore with different kinds of graphic cells and rule sets. I’ll let you know how it evolves!
thanks for all your help!
Yes! that’s a project i’ve been working with during he last year and a half and which evolved into different branches.
It all started as a “generative sonified spectrograms” machine which works exactly as you described: height is pitch, brightness is volume. I generate the graphics with Processing, and sonify them immediately after with max/msp following those rules. The spectrogram generation and sonification is briefly explained on the video here: FORMS - Screen Ensemble
The logical evolution of that first project was replacing additive sound synthesis with “human sonification”. The color lines you’ve seen on the previous video are translated into music by the string quartet. The sonification rules are similar: height is pitch, color is the instrument (violin 1, violin 2, viola, cello), and line weight is volume. Again, this graphic scores are generated in Processing, but this time sonified by humans
As of today i’m still composing for this concert… 17 minutes by now, but heading to a 35minutes long show. Hopefully it would be completed for mid 2022!
Thanks for you interest on it!