How to use characters (and assign tasks to these characters) from a .txt file in Processing

We have a project called “generative art”.

Our goal is to make a program, and that program creates something totally random that works as a form of art.

My idea was the following, and I wanted to base it around the subject ‘Gaming’.

I want to start off drawing a line, let’s say it moves over the Y-axis ( up an ddown) and starts with a certain color.

I want to record keyPressed/inputs from when you are gaming with some sort of KeyLogger, which regenerates a .txt file. Let processing read that .txt and assign certain stuff to that specific key. Basically, let the program follow the assigned variables while gaming. After a 10 minute session (or so), I can see what kind of “art” the program has created.

for instance :

Processing reads .txt file

if key == ‘w’, then make line thicker

if key == ‘a’, change color

and so on.

Then when Processing reads the .txt file, it will draw what has been given to it.

TekenLine drawing;
//String[] data;
String[] buttonLog;
//char[] keys = {'a','w','s','d'};

void setup() {
  size(800, 800);
  background(255);
  //buttonLog = loadStrings("buttonLog.txt");
  //data = split(buttonLog[0], ',');
  drawing = new TekenLine();
}

void draw() {
  drawing.drawLine();
  drawing.moveLine();
  drawing.bounceLine();
}

with the class:

Class TekenLine {

  float x;
  float y;
  float lineWidth;
  float lineHeight;
  float xSnelheid;
  float ySnelheid;
  color lineColorR;
  color lineColorG;
  color lineColorB;

  TekenLine() {

    x = width/2;
    y = width/2;
    lineWidth = 10;
    lineHeight = 10;
    xSnelheid = 2;
    ySnelheid = 2;
    lineColorR = 0;
    lineColorG = 0;
    lineColorB = 0;
  }

  void drawLine() {
    fill(lineColorR, lineColorG, lineColorB);
    rect(x, y, lineWidth, lineHeight, 20);
  }

  void moveLine() {
    x += xSnelheid;
    
  }

  void bounceLine() {
    if (x >= width || x <= 0) {
      xSnelheid = -xSnelheid;
    }
    if (y >= height || y <= 0) {
      ySnelheid = -ySnelheid;
    }
  }
}

I used a certain code in PowerShell to track some keys. I generated a small .txt file that came up with the following input:

"dsdadadasdasdasdasdasdasdasaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" and this is just of less than 10 seconds of button mashing/holding.

I know how to let the program fetch the .txt file, but not how to go further with setting up the keys. I believed someone mentioned that I had to make an Array, but no clue how to go further on that after a few attempts.

How could I tackle the following issue?

just an example that might help you wrap your head around what to do with the data once you have it in processing.

exampleOut

String fakeData;
String chars;
int charIndex;
int x, y;
int gridCellSize;
int gridCellSizeHalf;
int gridWidth;
int gridHeight;

void setup() {
  size(480, 480, P2D);
  frameRate(10);
  
  chars = "wasd";
  //generate some fake data
  //this will just be replaced the loadstrings call
  for(int i = 0; i < 100; i++) {
    fakeData += chars.charAt(int(random(chars.length())));
  }
  
  gridCellSize = 32;
  gridCellSizeHalf = gridCellSize / 2;
  gridWidth = width / gridCellSize;
  gridHeight = height / gridCellSize;
  x = gridWidth / 2;
  y = gridHeight / 2;
}

void draw() {
  //background(200);
  
  switch(fakeData.charAt(charIndex)) {
    case 'w':
      //do that thing you do
      y = y - 1 < 0 ? gridHeight - 1 : y - 1;
      drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 0);
      break;
    case 'a':
      //do that thing you do
      x = x - 1 < 0 ? gridWidth - 1 : x - 1;
      drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 270);
      break;
    case 's':
      //do that thing you do
      y = y + 1 > gridHeight - 1 ? 0 : y + 1;
      drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 180);
      break;
    case 'd':
      //do that thing you do
      x = x + 1 > gridWidth - 1 ? 0 : x + 1;
      drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 90);
      break;
  }
  charIndex = (charIndex + 1) % fakeData.length();
}

//this nice bit of code is taken from: https://forum.processing.org/one/topic/drawing-an-arrow#25080000001036352.html
void drawArrow(int cx, int cy, int len, float angle){
  pushMatrix();
  translate(cx, cy);
  rotate(radians(angle));
  line(0,0,len, 0);
  line(len, 0, len - 8, -8);
  line(len, 0, len - 8, 8);
  popMatrix();
}

Thank you. I will give it a go and see if i can get it to work

Okay so I have been giving your code a look and been trying a few things here and there, but I cannot seem to figure out the next step on how to procceed (more due my lack of knowledge). I have been googling around a bit but still cannot really find the answer I am looking for.

  1. It seems that you were able to pick up specific chars. While I try to do so, it says it “cannot convert string [] to char” (I still basically have the same code as in my example, and even tried replicating some of what you wrote except for the int value, because I do not know honestly what’s it for). I have also been eye-ing your code, but I unfortunately lack the knowledge to understand a few things on how you did it. you mentioned in your code the following :
//generate some fake data
  //this will just be replaced the loadstrings call
  for(int i = 0; i < 100; i++) {
    fakeData += chars.charAt(int(random(chars.length())));
  }

i see that you wrote “this will just be replaced with the loadstrings call”. But I am confused: Do I need to leave the for-loop in there? do i need to do something else. I am unfortunately very stuck here.

How would I continue from the LoadStrings point?

i just used the for loop to generate a random string of data instead of having to read a text file of keylogged data.

basically you would replace that with something like

keylogData = loadStrings("keylogData.txt")[0];

and “keylogData.txt” will be a file in your sketch folder with all the data you have captured from the game or whatever source you decide to use. (this assumes it is all written on a single line btw)

so basically it would look the same but with a few adjustments to read from a file instead.

String keylogData;
int charIndex;
int x, y;
int gridCellSize;
int gridCellSizeHalf;
int gridWidth;
int gridHeight;

void setup() {
  size(480, 480, P2D);
  frameRate(10);

  gridCellSize = 32;
  gridCellSizeHalf = gridCellSize / 2;
  gridWidth = width / gridCellSize;
  gridHeight = height / gridCellSize;
  x = gridWidth / 2;
  y = gridHeight / 2;
    
  keylogData = loadStrings("keylogData.txt")[0];
}

void draw() {
  //background(200);

  switch(keylogData.charAt(charIndex)) {
  case 'w':
    //do that thing you do
    y = y - 1 < 0 ? gridHeight - 1 : y - 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 0);
    break;
  case 'a':
    //do that thing you do
    x = x - 1 < 0 ? gridWidth - 1 : x - 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 270);
    break;
  case 's':
    //do that thing you do
    y = y + 1 > gridHeight - 1 ? 0 : y + 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 180);
    break;
  case 'd':
    //do that thing you do
    x = x + 1 > gridWidth - 1 ? 0 : x + 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 90);
    break;
  }
  charIndex = (charIndex + 1) % keylogData.length();
}

//this nice bit of code is taken from: https://forum.processing.org/one/topic/drawing-an-arrow#25080000001036352.html
void drawArrow(int cx, int cy, int len, float angle) {
  pushMatrix();
  translate(cx, cy);
  rotate(radians(angle));
  line(0, 0, len, 0);
  line(len, 0, len - 8, -8);
  line(len, 0, len - 8, 8);
  popMatrix();
}

and the charIndex is just so it isnt all drawn in one pass but revealed over multiple frames. so it just iterates through the chars and will redraw the entire file over and over. also the switch in the draw method just gets the character of the string at the current index (charIndex) and acts according to what it is.

1 Like

Thank you so much, that made it much more clear for me :smile:

One last question

If I want to delete charIndex lnteger and the whole “charIndex = (charIndex + 1) % keylogData.length();” line. AKA, draw only once (from my understanding of your explanation). What kind of value would end up inbetween the brackets of switch()?

So:

  1. Load text (keylogData)
  2. draw once with sketch stuff.

or can i not remove it?

just lose the charIndex and iterate over the entire string.

void draw() {
  //background(200);
int len = keylogData.length();
for(int i = 0; i < len; i++) {

  switch(keylogData.charAt(i)) {
  case 'w':
    //do that thing you do
    y = y - 1 < 0 ? gridHeight - 1 : y - 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 0);
    break;
  case 'a':
    //do that thing you do
    x = x - 1 < 0 ? gridWidth - 1 : x - 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 270);
    break;
  case 's':
    //do that thing you do
    y = y + 1 > gridHeight - 1 ? 0 : y + 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 180);
    break;
  case 'd':
    //do that thing you do
    x = x + 1 > gridWidth - 1 ? 0 : x + 1;
    drawArrow(x * gridCellSize, y * gridCellSize, gridCellSize, 90);
    break;
  }
}
}

of course you don’t have to draw to a grid you can do whatever you want.

1 Like

You are amazing. I feels pretty dumb that I could not figure it out myself what so ever I tried.

thank you

all good. you’ll get there just keep plugging away at it.

I made sure to add the delay to my program (charIndex). I noticed when running it without it, the drawing would go extremly fast.

It works perfectly like I wanted to. I cannot thank you enough

1 Like

Glad it worked out for you!

One thing you might want to consider is mapping letters from your log randomly against operations in your code. So, rather than your program hardcoding what it will do for ‘w’, ‘a’, ‘s’, ‘d’, it does something like this:

  1. load the file
  2. loop through the characters, check your StringList to see if you have recorded it with .hasValue(), and if not, add it to the StringList with .append(). Now you have a list with one of each letter, in the order first encountered.
  3. shuffle the StringList with .shuffle(). You can make the result stable by assigning a randomSeed() – or leave it randomly different on each run.
  4. now, instead of case ‘w’, switch on case myStringList[0], case myStringList[1], etc.

Now characters from your log are mapped randomly against operations in your code. You will need to have at least 4 different characters in your log, but you could have more and the top 4 will randomly be chosen.

Sounds like a fun an interesting concept!

Will see if I can work it out.

Quick question, does anything need to be inbetween the brackets of .hasValue() or does it stay empty?

https://processing.org/reference/StringList.html
https://processing.org/reference/StringList_hasValue_.html
it checks if a “record” with a specific content exists

1 Like

That is correct – loop over keylogData.length(), get each character with c=keylogData.charAt(i), check your list with if(!myKeys.hasValue(str(c))), and then myKeys.append(str(c)).

The goal is to build a unique list of key entries based on the contents of the log file. There are many ways of doing this in Processing, and more generally in Java. StringList isn’t automatically unique – that’s why it is necessary to check each time before you add.

There are other ways. You could force your list to be unique automatically by using HashMap (see Processing reference) or by using one of the Set datatypes in Java – then you could add all the characters without checking and there would be no duplicates. However, then you wouldn’t get the built-in shuffle method that StringList has, so instead of writing extra code to check uniqueness, you would need to write extra code to shuffle.

1 Like