kerjos-Speech

For this project, I was thinking about a comment Golan made in class, about how bots on Twitter are interfering with civil discourse, by, I imagined, taking up the space (measured in words and the time people spend reading online) of humans. I thought that our efforts to identify bots online was a concept that could be deployed as a game.

In my game, you participate in an exchange with another human player, whose computer you pass a spoken word to from your own. Your opponent is disguised among a chorus of bots who chime in with their own words each turn of the exchange. After some time, you’re given the chance to try and identify your human player, based on the exchange that you’ve been having.

I’ve enjoyed testing out this prototype so far with friends and family in other states, and ideally, I think that it would make a nice web game hosted online.

You can try it for yourself, although there’s a start-up bug that’s picky about the order that players log on. Load the page for yourself first, and then share the url with a friend on another computer. You can always both close your tabs and then travel to the link again if it doesn’t work.

Here’s a page from my sketchbook mapping out the game:

And here’s my code:

// The speech recognizer
var mySpeechRecognizer;
var mostRecentSpokenWord;
var mostRecentConfidence;
 
// The RiTa Lexicon
var myRitaLexicon;
 
//Socket
var socket = io.connect(":30000?sketch=-1000");
var userNumber = 0;
 
var playerWord = "nothing";
var botWord = "nothing";
var speakers;
var numSpeakers = 4;
var numPlayers = 2;
 
var humanRevealed = false;
var answerCorrect = false;
var showResultText = false;
var opponentAnswered = false;
var IAnswered = false;
var opponentReadyToReset = false;
var myTurn = false;
 
var timer = 0;
var timeUntilQuiz = 3000; //calls of draw function
var quizTime = false;
 
var fontSize = 12;
var charWidth = 30;
var charHeight = 30;
var charWordMargin = 20;
var eyeSpacing = 15;
var eyeRadius = 5;
var mouthSpacing = 5;
var mouthHeight = 5;
var earRadius = 8;
var botMouthSquare = 3;
var bubbleWidth = 100;
var bubbleHeight = 30;
var bubbleTailWidth = 10;
var bubbleTailHeight = 10;
var newGameWidth = 180;
var newGameHeight = 40;
 
//===============================================
function Speaker(number, type) {
	var speaker = {
		number: number,
		type: type,
		voice: null,
		word: "",
	}
	return speaker;
}
 
//===============================================
function setup() {
	createCanvas(windowWidth, windowHeight);
	//speech stuff:
	mostRecentConfidence = 0;
	mostRecentSpokenWord = "";
	initializeMySpeechRecognizer();
	myTurn = true;
	// Create the RiTa lexicon
	myRitaLexicon = new RiLexicon();
 
	socket.on('connection', setNewPlayer);
	socket.emit('connection');
 
	socket.on("sendToOtherPlayer",receiveOpponentInput);
	socket.on("updatePlayerNum",updatePlayerNum);
	socket.on("revealHuman",revealHuman);
	socket.on("switchQuizTime",switchQuizTime);
	socket.on('submitAnswer',receiveAnswer);
	socket.on('resetGame',resetGame);
	socket.on('readyToReset',setReadyToReset);
	socket.on('endTimer',endTimer);
	socket.on('updateSpeakers',updateSpeakers);
 
	if (speakers == null) {
		//Initialize speakers array:
		speakers = [];
		for (var i=0; i<numSpeakers; i++) {
			speakers[i] = null;	
		}
		//Initialize Player One:
		index = getIndex();
		speakers[index] = Speaker(userNumber, 'human');
 
		//Initialize bots:
		var botNumber = -1;
		for (var i=0; i<speakers.length-numPlayers; i++) {
			var botIndex = getIndex();
			speakers[botIndex] = Speaker(botNumber, 'robot');
			botNumber -= 1;
		}
	}
 
	textFont('Helvetica');
	textSize(fontSize);
}
 
//-----------Socket Stuff-------------------
function setNewPlayer() {
	var newPlayerNum = userNumber + 1;
	socket.emit('updatePlayerNum',newPlayerNum);
	var newPlayerIndex = getIndex();
	speakers[newPlayerIndex] = Speaker(newPlayerNum, 'human');
	shuffleSpeakers();
	socket.emit('updateSpeakers',speakers);
	myTurn = false;
}
 
function updatePlayerNum(playerNum) {
	userNumber = playerNum;
}
 
function revealHuman() {
	humanRevealed = !humanRevealed;
}
 
function switchQuizTime() {
	quizTime = !quizTime;
}
 
function receiveAnswer() {
	opponentAnswered = true;
}
 
function resetGame() {
	console.log('resetting game');
	if (userNumber == numPlayers-1) {
		myTurn = true;
		initializeMySpeechRecognizer();
		shuffleSpeakers();
		socket.emit('updateSpeakers',speakers);
	}
	humanRevealed = false;
	answerCorrect = false;
	showResultText = false;
	opponentAnswered = false;
	IAnswered = false;
	opponentReadyToReset = false;
	timer = 0;
	quizTime = false;
}
 
function setReadyToReset() {
	opponentReadyToReset = true;
}
 
function endTimer() {
	timer = timeUntilQuiz;
}
 
function updateSpeakers(speakerList) {
	speakers = speakerList;
}
 
function getIndex() {
	available = [];
	for (var i=0; i<speakers.length; i++) {
		if (speakers[i] == null) {
			available.push(i);
		}
	}
	var j = floor(random(0,available.length));
	return available[j];
}
 
function shuffleSpeakers() {
	var aux = []
	for (var i=0; i<speakers.length; i++) {
		aux[i] = speakers[i];
		speakers[i] = null;
	}
	for (var i=0; i<aux.length; i++) { var j = getIndex(); speakers[j] = aux[i]; } } function parseAnswer(speaker) { if (speaker.type == 'human') { answerCorrect = true; } } //-----------Speech Stuff------------------- function initializeMySpeechRecognizer(){ mySpeechRecognizer = new p5.SpeechRec('en-US'); mySpeechRecognizer.continuous = true; // do continuous recognition mySpeechRecognizer.interimResults = false; // allow partial recognition mySpeechRecognizer.onResult = parseResult; // recognition callback mySpeechRecognizer.start(); // start engine console.log(mySpeechRecognizer); console.log("initalized"); } function parseResult() { if (!myTurn) { console.log('will not parse.'); return; } console.log('parsing...'); mostRecentConfidence = mySpeechRecognizer.resultConfidence; if (mostRecentConfidence > 0.0){ // some confidence threshold...
		console.log (mySpeechRecognizer.resultString);
 
		// The Recognition system will often append words into phrases.
		// So the hack here is to only use the last word:
		mostRecentSpokenWord = mySpeechRecognizer.resultString.split(' ').pop();
		socket.emit("sendToOtherPlayer",mostRecentSpokenWord,userNumber);
		myTurn = false;
	}
}
 
function receiveOpponentInput(input,number) {
	//First: Show Opponent's Word and Generate Bot Words.
	for (var i=0; i<speakers.length; i++) { var speaker = speakers[i]; if (speaker != null) { if (speaker.number == number) { speaker.word = input; } if (speaker.type == 'robot') { speaker.word = getBotWord(); } } } socket.emit('updateSpeakers',speakers); //Second: If timer up, show question and wait for answer. if (timer>=timeUntilQuiz) { //This is an event.
		timer = 0;
		quizTime = true;
		socket.emit('switchQuizTime');
	} else {
		//Else, pass turn along.
		initializeMySpeechRecognizer();
		myTurn = true;
	}
}
 
function getBotWord() {
	var word = myRitaLexicon.randomWord();
	if (random(0,1)>0.85) {
			word = word.charAt(0).toUpperCase() + word.slice(1);
		//Thank you Paulund for giving me this with a quick Google search.
		//https://paulund.co.uk/capitalize-first-letter-string-javascript
	}
	return word;
}
 
//===============================================
function mousePressed() {
	if (quizTime) {
		console.log('clicking anywhere');
		if ((opponentAnswered) && (showResultText)) {
			var bX = width/2;
			var bY = (height/2)-(2*charHeight)-charWordMargin;
			var bW = newGameWidth;
			var bH = newGameHeight;
			if ((mouseX >= bX-(bW/2)) && (mouseX <= bX+(bW/2)) && (mouseY >= bY-(bH/2)) && (mouseY <= bY+(bH/2))) {
				console.log("button clicked");
				if (opponentReadyToReset) {
					resetGame();
					socket.emit('resetGame');
				} else {
					console.log('waiting for opponent');
					IAnswered = true;
					socket.emit('readyToReset');
				}
			}
		}
		for (var i=0; i<speakers.length; i++) { var speaker = speakers[i]; if (speaker.number == userNumber) { //Don't respond to clicking on yourself. continue; } var cx = ((width/(numSpeakers+2))*(i+1)); var cy = height/2; if ((mouseX >= cx-(charWidth/2)) && (mouseX <= cx+(charWidth/2)) && (mouseY >= cy-(charHeight/2)) && (mouseY <= cy+(charHeight/2))) { console.log("clicked"); parseAnswer(speaker); showResultText = true; revealHuman(); socket.emit('submitAnswer'); } } } } function keyPressed() { var question; if (keyCode==UP_ARROW) { switchQuizTime(); socket.emit('switchQuizTime'); if (quizTime==false) { initializeMySpeechRecognizer(); myTurn = true; } } else if (keyCode==RIGHT_ARROW) { question = "hi"; socket.emit("sendToOtherPlayer",question); } else if (keyCode==LEFT_ARROW) { revealHuman(); socket.emit('revealHuman') } else if (keyCode==DOWN_ARROW) { console.log('down pressed'); console.log(speakers); } } //=============================================== function draw() { if ((userNumber == numPlayers-1) && (!quizTime)) { timer += 1; if (timer >= timeUntilQuiz) {
			timer = timeUntilQuiz;
			socket.emit('endTimer');
		}
	}
 
	background(255);
	//text(userNumber,30,30);
	var myTurnFill;
	textAlign(LEFT);
	textSize(fontSize);
	fill(0);
	noStroke();
	if (myTurn) {
		myTurnFill = color(0,255,0);
		text("It's your turn! Say something!",60,40);
	} else {
		myTurnFill = color(255,0,0);
		text("Waiting for your opponent's word.",60,40);
	}
	fill(myTurnFill);
	ellipse(50,35,5,5);
	//text("Timer: " + timer, 30,60);
	//text("Quiz Time? " + quizTime, 30,90);
	drawInstructions();
 
	if (quizTime) {
		if (showResultText) {
			drawResultText();
			if (opponentAnswered) {
				if (IAnswered) {
					drawWaitingText();
				} else {
					drawNewGameButton();
				}
			}
		} else {
			drawQuiz();
		}
	}
	for (var i=0; i<speakers.length; i++) {
		var speaker = speakers[i];
		drawSpeaker(speaker, i);
	}
}
 
function drawSpeaker(speaker, i) {
	if (speaker == null) {
		noStroke();
		fill(0);
		ellipse(((width/(numSpeakers+2))*(i+1)),height/2,10,10);
	} else if (speaker.type == 'human') {
		drawHuman(speaker, i);
	} else if (speaker.type == 'robot') {
		drawBot(speaker, i);
	}
}
 
function drawHuman(speaker, i) {
	noStroke();
	if (speaker.number == userNumber) { //Draw yourself.
		drawYourself(i);
	} else { //Draw your opponent.
		if (humanRevealed) {
			drawWord(speaker.word, i); //Uncomment and replace with winning text.
			if (answerCorrect) {
				drawHappyOpponent(i);
			} else { //answer incorrect
				drawSadOpponent(i);
			}
		} else { //Opponent still disguised:
			drawBot(speaker, i);
		}
	}
}
 
function drawBot(speaker, i) {
	drawWord(speaker.word, i);
	var cx = (width/(numSpeakers+2))*(i+1);
	var cy = height/2;
	fill(73, 85, 102);
	noStroke();
	rectMode(CENTER);
	rect(cx,cy,charWidth,charHeight);
	fill(40, 255, 212);
	rect(cx-(eyeSpacing/2),cy,eyeRadius,eyeRadius);
	rect(cx+(eyeSpacing/2),cy,eyeRadius,eyeRadius);
	//fill(255, 193, 38);
	rect(cx-(eyeSpacing/2),cy+mouthSpacing+mouthHeight,
			 botMouthSquare,botMouthSquare);
	rect(cx-(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
			 botMouthSquare,botMouthSquare);
	rect(cx,cy+mouthSpacing+mouthHeight,
			 botMouthSquare,botMouthSquare);
	rect(cx+(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
			 botMouthSquare,botMouthSquare);
	rect(cx+(eyeSpacing/2),cy+mouthSpacing+mouthHeight,
			 botMouthSquare,botMouthSquare);
}
 
function drawWord(word, i) {
	if ((!myTurn) || (word=="")) {
		return
	}
	var cx = (width/(numSpeakers+2))*(i+1);
	var cy = (height/2)-(charHeight)-charWordMargin;
	var w = bubbleWidth;
	var h = bubbleHeight;
	var tw = bubbleTailWidth;
	var th = bubbleTailHeight;
	fill(255);
	stroke(0);
	beginShape();
	vertex(cx-(w/2),cy-(h/2));
	vertex(cx+(w/2),cy-(h/2));
	vertex(cx+(w/2),cy+(h/2));
	vertex(cx+(tw/2),cy+(h/2));
	vertex(cx,cy+(h/2)+th);
	vertex(cx-(tw/2),cy+(h/2));
	vertex(cx-(w/2),cy+(h/2));
	vertex(cx-(w/2),cy-(h/2));
	endShape();
	fill(0);
	noStroke();
	textAlign(CENTER);
	text(word,cx,cy+(fontSize/4));
}
 
function drawYourself(i) {
	var cx = (width/(numSpeakers+2))*(i+1);
	var cy = height/2;
	fill(255,238,0);
	ellipse(cx,cy,charWidth,charHeight);
	ellipse(cx-(charWidth/2),cy+(mouthSpacing/3),earRadius);
	ellipse(cx+(charWidth/2),cy+(mouthSpacing/3),earRadius);
	fill(75);
	ellipse(cx-(eyeSpacing/2),cy,eyeRadius);
	ellipse(cx+(eyeSpacing/2),cy,eyeRadius);
	noFill();
	stroke(75);
	strokeWeight(2);
	bezier(cx-(eyeSpacing/2),cy+mouthSpacing,
				 cx-(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
				 cx+(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
				 cx+(eyeSpacing/2),cy+mouthSpacing);
	strokeWeight(1);
}
 
function drawHappyOpponent(i) {
	var cx = (width/(numSpeakers+2))*(i+1);
	var cy = height/2;
	fill(255,199,0);
	ellipse(cx,cy,charWidth,charHeight);
	ellipse(cx-(charWidth/2),cy+(mouthSpacing/3),earRadius);
	ellipse(cx+(charWidth/2),cy+(mouthSpacing/3),earRadius);
	fill(50);
	ellipse(cx-(eyeSpacing/2),cy,eyeRadius);
	ellipse(cx+(eyeSpacing/2),cy,eyeRadius);
	noFill();
	stroke(50);
	strokeWeight(2);
	bezier(cx-(eyeSpacing/2),cy+mouthSpacing,
				 cx-(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
				 cx+(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
				 cx+(eyeSpacing/2),cy+mouthSpacing);
	strokeWeight(1);
}
 
function drawSadOpponent(i) {
	var cx = (width/(numSpeakers+2))*(i+1);
	var cy = height/2;
	fill(255,199,0);
	ellipse(cx,cy,charWidth,charHeight);
	ellipse(cx-(charWidth/2),cy+(mouthSpacing/3),earRadius);
	ellipse(cx+(charWidth/2),cy+(mouthSpacing/3),earRadius);
	fill(50);
	ellipse(cx-(eyeSpacing/2),cy,eyeRadius);
	ellipse(cx+(eyeSpacing/2),cy,eyeRadius);
	noFill();
	stroke(50);
	strokeWeight(2);
	bezier(cx-(eyeSpacing/4),cy+mouthSpacing+mouthHeight,
				 cx-(eyeSpacing/2),cy+mouthSpacing,
				 cx+(eyeSpacing/2),cy+mouthSpacing,
				 cx+(eyeSpacing/4),cy+mouthSpacing+mouthHeight);
	strokeWeight(1);
}
 
function drawQuiz() {
	var quiz = "Which bot is the other human?";
	fill(0);
	noStroke();
	textAlign(CENTER);
	textSize(fontSize*2);
	text(quiz,width/2,(height/2)-150);
}
 
function drawResultText() {
	var result;
	if (answerCorrect) {
		result = "You got it right!";
	} else {
		result = "Nope! That's a bot!";
	}
	fill(0);
	textAlign(CENTER);
	textSize(fontSize*2)
	text(result,width/2,(height/2)-150);
}
 
function drawNewGameButton() {
	var buttonText = "Start New Round?";
	fill(255);
	stroke(0);
	rectMode(CENTER);
	rect(width/2,(height/2)-(2*charHeight)-charWordMargin,
			 newGameWidth,newGameHeight);
	fill(0);
	noStroke();
	textAlign(CENTER);
	textSize(fontSize*(3/2))
	text(buttonText,width/2,
				(height/2)-(2*charHeight)-charWordMargin+(fontSize*(3/8)));
}
 
function drawWaitingText() {
	var waitingText = "Waiting for Opponent...";
	fill(0);
	noStroke();
	textAlign(CENTER);
	textSize(fontSize);
	text(waitingText,width/2,(height/2)-100);
}
 
function drawInstructions() {
	var line1 = "In this game, you pass words between your";
	var line2 = "computer and your opponent's. Your";
	var line3 = "opponent is disguised as one of the bots,";
	var line4 = "who are also passing you words. After 30";
	var line5 = "seconds, guess which bot is really your";
	var line6 = "opponent!";
	var lines = [line1,line2,line3,line4,line5,line6];
	var yStart = 70;
	var x = 50;
	textSize(fontSize);
	textAlign(LEFT);
	fill(150);
	noStroke();
	for (var i=0; i<lines.length; i++) {
		text(lines[i],x,yStart+(20*i));
	}
}