DroidCo. Final

For the final stage of my project, I finished the display and game logic of DroidCo. As a final product, DroidCo. allows two players to use OSC to “write code” (by pressing a predetermined selection of buttons) that their droids will execute to compete. The goal of the game is to convert every droid on the grid onto your team.

The code input screen, with the option to use the same code from the previous round or rewrite new code
The actual game screen, which shows the droids moving about and displays the current turn
The victory screen! This scene plays after a team has converted all the droids or turn 350 has been reached.
function main()
{
	var worldState = JSON.parse(arguments[0]);
	var droid = arguments[1];
	var startIndex = (droid * 6) + 2;
	var team = worldState[startIndex + 1];
	var x = worldState[startIndex + 2];
	var y = worldState[startIndex + 3];
	var dir = worldState[startIndex + 4];
	var red;
	var blue;
	var hor;
	var ver;
	var rot;
	switch (team){
		case 1:
			blue = 50;
			red = -100;
			break;
		case 2:
			blue = -100;
			red = 50;
			break;
	}
	switch (x){
		case 0:
			hor = -45;
			break;
		case 1:
			hor = -35;
			break;
		case 2:
			hor = -25;
			break;
		case 3:
			hor = -15;
			break;
		case 4:
			hor = -5;
			break;
		case 5:
			hor = 5;
			break;
		case 6:
			hor = 15;
			break;
		case 7:
			hor = 25;
			break;
		case 8:
			hor = 35;
			break;
		case 9:
			hor = 45;
			break;
	}
	switch (y){
		case 0:
			ver = 45;
			break;
		case 1:
			ver = 35;
			break;
		case 2:
			ver = 25;
			break;
		case 3:
			ver = 15;
			break;
		case 4:
			ver = 5;
			break;
		case 5:
			ver = -5;
			break;
		case 6:
			ver = -15;
			break;
		case 7:
			ver = -25;
			break;
		case 8:
			ver = -35;
			break;
		case 9:
			ver = -45;
			break;
	}
	switch(dir){
		case 0:
			rot = 180;
			break;
		case 90:
			rot = 270;
			break;
		case 180:
			rot = 0;
			break;
		case 270:
			rot = 90;
			break;
	}
	return [red, blue, hor, ver, rot];
}

Above is the code for the “droid parser” which converts each droid’s worldState data into values that Isadora can understand to display the droid.

Overall, the game was a major success. It ran with only minor bugs and I accomplished most of what I wanted to do with this project. The code was rather buggy (I underestimated the strain new users could put on my code). I also ran into several problems with players’ code not executing exactly as they wrote it. These bugs are definitely due to the String Constructor (Isadora does not handle strings very well), but I think I can work out the kinks.


Cycle 2 – DroidCo. Interface Development

For cycle two, I finished the development of the OSC interface for DroidCo. The interface consisted of a set of buttons that sent keywords to Isadora which would then be used to generate the virtual code for each team’s droid to run.

The interface (available in both an iPhone/iPod version and iPad version) consisted of two pages. The first contained the END command to complete code blocks, IF and WHILE to create conditional blocks, action keywords to trigger turn actions, and a delete button to remove lines of code. The second page contained all the conditionals that would be used with the IF and WHILE blocks.

To process these OSC inputs, I created a String builder javascript program. This program takes the next OSC input and adds it to a code string (the string the compiler from cycle 1 uses to create the virtual code) and a display string that shows the user their code (with proper indentation). Below is the code for this program.

function main(){
	var codeString = arguments[0];
	var displayString = arguments[1];
	var next = arguments[2];
	var ends = arguments[3];
	var i;
	switch(next){
		case "END ":
			codeString = codeString.concat(next);
			ends = ends - 1;
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "IF":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			ends = ends + 1;
			break;
		case "WHILE":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			ends = ends + 1;
			break;
		case "move ":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "turn-left ":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "turn-right ":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "skip ":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "hack ":
			codeString = codeString.concat(next);
			for (i = 0; i < ends; i++){
				displayString = displayString.concat("\t");
			}
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		case "back":
			if(codeString != "BEGIN "){
				if(codeString.substring(codeString.length - 4) == "END "){
					ends = ends + 1;
				}
				codeString = codeString.substring(0, codeString.lastIndexOf(" "));
				codeString = codeString.substring(0, codeString.lastIndexOf(" ")+1);
				displayString = displayString.substring(0, displayString.lastIndexOf(" "));
				displayString = displayString.substring(0, displayString.lastIndexOf(" ")+2);
			}
			break;
		default:
			codeString = codeString.concat(next);
			displayString = displayString.concat(next);
			displayString = displayString.concat("\n");
			break;
		}	
	var ret = new Array(codeString, displayString, ends);
	return ret;
}


Cycle 1 – DroidCo. Backend Development

For cycle 1, I built the backend system for a game I’ve been referring to as “DroidCo.” The goal of the game is to give non-programmers the opportunity to program their own AI in a head-to-head “code-off.”

The game starts with both players in front of sets of buttons (either digital or physical via makey makey). The buttons have keywords for the DroidCo. programming language. By pressing these buttons, both players build a program that each of their team’s droid will run. Here’s what a program might look like:

BEGIN
   IF-next-is-enemy
      hack
   END
   WHILE-next-is-empty
      move
   END
END

In addition to BEGIN and END, here are the keywords for the DroidCo. language:

After both players have completed their code, the second phase of the game begins. In this phase, a grid of spaces is populated by droids, half of which belong to one player and the other half belonging to the other player. Each second a “turn” goes by in which every droid takes an action determined by the code written by their owner. One of these actions could be “hack” which causes a droid to convert an enemy droid in the space it is facing into an ally droid. The goal is to create droids that “hack” all of your opponent’s droids, putting them all under your control.

The backend development is sorta an involved process to explain, so I may put the gritty details on my personal WordPress later, but here’s a boiled down version. We start with a long string that represents the program. For the sample program above it would be: “BEGIN IF-next-is-enemy hack END WHILE-next-is-empty move END END “. We give this string to the tokenizer that splits it up into an array of individual words. Here’s the tokenizer:

function tokenizer(tokenString){
	var tokens = [];
	var index = 0;
	var nextSpace = 0;
	while(index < tokenString.length){
		nextSpace = tokenString.indexOf(" ", index);
		tokens[tokens.length] = tokenString.substring(index, nextSpace);
		index = nextSpace+1;
	}
	return tokens;
}

This array is then given to the code generator, which converts it to a code “roadmap” in the form of an array of integers. Here’s the code generator:

function codeGenerator(code, tokens){
	var startIndex = code.length;
	var nextToken = tokens.shift();
	var dummy;
	var whileloop;
	while (tokens.length > 0 && nextToken != "END"){
		switch (nextToken){
			case "BEGIN":
				break;
			case "move":
				code[code.length] = -1;
				break;
			case "turn-right":
				code[code.length] = -2;
				break;
			case "turn-left":
				code[code.length] = -3;
				break;
			case "hack":
				code[code.length] = -4;
				break;
			case "skip":
				code[code.length] = -5;
				break;
			case "IF—next-is-ally":
				code[code.length] = -6;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-not-ally":
				code[code.length] = -7;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-enemy":
				code[code.length] = -8;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-not-enemy":
				code[code.length] = -9;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-wall":
				code[code.length] = -10;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-not-wall":
				code[code.length] = -11;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-empty":
				code[code.length] = -12;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "IF-next-is-not-empty":
				code[code.length] = -13;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-ally":
				whileloop = code.length;
				code[code.length] = -6;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-not-ally":
				whileloop = code.length;
				code[code.length] = -7;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-enemy":
				whileloop = code.length;
				code[code.length] = -8;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-not-enemy":
				whileloop = code.length;
				code[code.length] = -9;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-wall":
				whileloop = code.length;
				code[code.length] = -10;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-not-wall":
				whileloop = code.length;
				code[code.length] = -11;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-empty":
				whileloop = code.length;
				code[code.length] = -12;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
			case "WHILE-next-is-not-empty":
				whileloop = code.length;
				code[code.length] = -13;
				dummy = code.length;
				code[code.length] = -100;
				codeGenerator(code, tokens);
				code[code.length] = whileloop;
				code[dummy] = code.length;
				break;
		}
		nextToken = tokens.shift();
	}
}

This code is then given to the server, which stores both players’ codes and controls the game-state. Since the server is over 300 lines of code, I wont be posting it here. Nonetheless, each turn the server will run through the droids, allowing each of them to take an action to modify the game-state. Once they have all acted, it outputs the new game-state to both the display actors and back into itself so it can run the next round.


PP3 – Haunted Isadora

For PP3, I attempted to create the experience of talking to spirits through a computer interface. To do this, I used a simple sound level controller to move between scenes in Isadora.

When a user begins the program, the Isadora patch randomly generates wait times that the user must wait through before Isadora begins listening. Once the user speaks (or, to my dismay, when the background music plays… oops) the scenes progress and the “little girl” asks them to play hide and seek.

After a moment or two, a jump-scare pops up and the little girl has found you. Over-all I wasn’t super proud of the project. It could have had more substance (the original plan was to have multiple “entities” to talk to… but that didn’t come together). I learned a bit about background scene triggers and text actors so I suppose it wasn’t a total flub.


PP2 – Straight Talk

For pressure project 2, we were challenged to use audio to tell a narrative in one minute. My project attempted to tell the story of the AIDS crisis during the 1980s, specifically the relationship that straight anxiety about AIDS (or lack of anxiety in the case of the Reagan administration) related to the death-toll the crisis had on the lgbtq+ community. The main idea was to use entirely straight voices (other than my own) to show the way straight society looked at AIDS during the crisis.

The tone I took was largely critical as I selected news clips that highlighted some of the ridiculous fears that straight people had about the disease (“can my dog catch AIDS from a bone my neighbor handled?”). These sound clips were then edited together and played over a fairly unsettling atmospheric track in order to create a feeling of tension.

The next component consisted of me reading yearly AIDS death tolls in the U.S. during the 1980s and early 1990s. This was done simultaneously with the playing of the audio in a droning, mechanical fashion. Initially, this reading was going to be recorded and edited with the audio clips, but upon listening to my recording (which took over an hour of my 5 hour time constraint), I opted to read it live. I honestly didn’t have a plan as to how I would read it due to how last-minute this decision was, so I largely improvised. As such, the live performance portion could definitely be polished and refined into something more formal than someone sitting behind a computer screen.

The final component was a closing critique of the Reagan administration’s reaction to AIDS. At the end of the performance, I stopped reading the death totals and an audio clip played of reporter Lester Kinsolving asking Press Secretary Larry Speakes what the president was doing about the AIDS crisis. Mr. Speakes responds with a number of jokes which the other reports can be heard laughing at, and the clip ends with the Mr. Kinsolving asking if “the White House looks at this as a great joke?”

Second performance, perhaps not as good as the first

Overall I think the project was successful in eliciting an emotional response from its listeners. There was a moment of emotional silence after the first run-through that I didn’t expect. Perhaps this silence was reverence for those that died or everyone processing all the sound I had just thrown at them, but those few seconds made me proud that I could tell this tragic story and honor those that suffered through it.


PP 1 – Chillin’ with Planty

I had a lot of fun with pressure project one! I really didn’t know what to make until one morning I was watering one of my many plants (i.e. my children) when it struck me: a plant watering game.

“Planty” is a little plant sprite that moves left and right on the screen when you press ‘a’ and ‘d.’ Below is Planty’s user actor.

Moving Planty moves the projector he’s being shown on. Planty’s actor then outputs the horizontal position so the droplets can tell where he is.

Planty wants to collect droplets, which make a little “boop” noise when he catches them. Below is the user actor for one of the droplets.

Although its kinda messy, the gist is that a signal into the droplet starts its falling. Another input tells the droplet Planty’s horizontal location. If Planty’s horizontal position matches the droplet’s while it is at a height of -30 or less (Planty’s height), it sends out a signal that will trigger the “boop.”

The time constraint of the pressure project posed a bit of a challenge. This wasn’t because I didn’t have enough time, but rather I had trouble tracking my time. I typically work on things through multitasking, so it was hard to track exactly how much time I was spending on the project (e.g. sometimes I would work on it while holding office hours for a class I TA, often pausing my work with students and then returning to it once they left). I estimated that I worked a little less than four hours, but perhaps a better method of time-keeping is needed for the next project. Overall I think the project was a success and I’m very happy with what I was able to produce.

Water Planty and he will love you forever <3

Reflection on the Magic Window

https://dems.asc.ohio-state.edu/?p=1735(opens in a new tab)

I’m bumping this project because I’m fascinated by the technical feat she has achieved. The way the buttermilk-coated window produces a dreamlike quality while with the projected image is an amazing sight to see.