Making Tetris in Twitter with Node.js

Making Tetris in Twitter with Node.js

Posted on Tuesday, 17th of August 2021

When I saw Twitter Plays Snake bot, I knew I should create something similar to that.
It took me a while to think of game, and at first I made Blackjack, but it didn't gain any attention so my second attempt was Tetris, and seems like people really love it. Check it out!
So, TL;DR: you can control how pieces fall by doing something to a tweet: comment to make it move left, retweet to rotate it and like to move it right.

Step 1) Get Developer account

So I created a new Twitter account, went to Developer Portal and, ugh, you must write down everything about your future project. It was a bit embarrassing and I wrote that I want to make a game where people can control it by comments, likes and retweets blah blah blah.
They emailed me with this:



Mmm, I saw the light green text, I guess it was the info they still needed.

I replied:



And on next day:



Yay, now time to suffer and learn how API works.

Step 2) Post your first 'Hello, world!'.

Yeah, it sounds really simple, but it actually took me a day of pain.
I was searching for good npm libraries for Twitter but for some reason all of them were saying that my credentials can't post tweets. I changed rights to "Read, Write, and Direct Messages" and it still didn't want to work!!!!!!
All tutorials in internet were just saying "lol just put in your key and secret key in and it'll start working", and then I've found out about 3-legged OAuth and implemented it to get access token to only realize it was all for nothing because you can get Access Token and Secret just by getting it in settings, god damn it.

I ended up using twitter-api-v2 library. And finally, the "Hello, world!" was posted:

import Twitter from 'twitter-api-v2';

const client = new Twitter.default({
	appKey: 'consumer key',
	appSecret: 'consumer secret',
	accessToken: 'access token',
	accessSecret: 'access secret',
});
await client.v1.tweet("Hello, world!");

Step 3) Tetris

I'm not gonna show how to make Tetris game itself, that would take too much space here and this article is more about making a Twitter bot. I have made Tetris before and I have functions like .down(), .right(), .rotate(), etc...
You'll need logic of Tetris that you can control via these functions. You could even find code of Tetris somewhere in internet and just use that.

Step 4) Connecting Tetris and Twitter

Now this is the most important part. When script runs, we must start a new game and send tweet, and start an interval that will run every 5 minutes.
I have an array of arrays called display that shows blocks in game. So gotta transform that into pretty string:

let out = "";
for(let i in display) {
    for(let j in display[i]) out += display[i][j]
    if(i == 2) out += ` New game.`;
    if(i == 3) out += ` Score: ${score}`;
    if(i == 4) out += ` Next: ${pNext.name}`;
    out += "
";
}
out += "💬=⬅️  🔁=🔄  ❤️=➡️";
		

Before posting, I must tell that Twitter doesn't allow duplicates of previous tweets. I don't know how much time must pass between duplicate tweets, but sometimes Tetris has 2 same states, and you'll get error if you won't handle that.
So, to avoid that, I've made variable index = 0 and this is the sending code:

try {
	await client.v1.tweet(out);
} catch(e) {
	out += ` ${index++}`;
	await client.v1.tweet(out);
}
		

So basically we just try to send tweet, and if it fails we add a number to end of tweet, increment it and send the tweet.

Well, we sent our start tweet, after 5 minutes bot will have to check for comment, like and retweet count, so let's implement that.

Getting like and retweet count is really easy, just have to get tweet ID and request tweet data:

// Get last tweet made by bot excluding replies and retweets
let lastTweet = (await client.v2.userTimeline('1424384057991995397', { exclude: 'replies,retweets' })).tweets[0];
// Get tweet data
lastTweet = await client.v1.get(`statuses/show.json`, {id: lastTweet.id});

let likes = lastTweet.favorite_count; // right let retweets = lastTweet.retweet_count; // spin

Main problem was in comment count, I was googling for API and couldn't find anything for a while til finally found Conversations API:

// Get last tweet made by bot excluding replies and retweets
let lastTweet = (await client.v2.userTimeline('1424384057991995397', { exclude: 'replies,retweets' })).tweets[0];
// Get Conversation ID of that tweet
let conv = await client.v2.get(`tweets?ids=${lastTweet.id}&tweet.fields=conversation_id`);
// Get list of comments with additional fields
conv = (await client.v2.get(`tweets/search/recent?query=conversation_id:${conv.data[0].conversation_id}&tweet.fields=in_reply_to_user_id,author_id`)).data;
// If there are no comments, there will be no .data array
if(!conv) conv = [];
		

We must ensure that it counts only 1 comment per person and only ones that reply to bot, not to other tweets. So first off I just filter array from tweets that don't reply to main tweet and then map array to consist of author ids, and then make a Set from that array so it deletes all duplicates from it and then create array again. A bit hacky but it works.

// Filter replies to other tweets, map it so it'll consists of author IDs, create Set which will remove all duplicates and create array again.
conv = [...new Set(
			conv
				.filter(i => i.in_reply_to_user_id === '1424384057991995397')
				.map(i => i.author_id)
		  )
];
// Array now only consists of unique author IDs so we can just get it's length
let comments = conv.length; // left
		

Now we have comments, retweets and likes counts, so we just do actions to the game:

if(likes > comments && likes === retweets) {
	// More likes than comments and equal to retweets
	// Right + Rotate
	p.moveRight();
	p.rotate();
} else if(comments > likes && comments === retweets) {
	// More comments than likes and equal to retweets
	// Left + Rotate
	p.moveLeft();
	p.rotate();
} else if(retweets > likes && retweets > comments) {
	// More retweets than likes and comments
	// Rotate
	p.rotate();
} else if(comments > likes) {
	// More comments than likes
	// Left
	p.moveLeft();
} else if(likes > comments) {
	// More likes than comments
	// Right
	p.moveRight();
}
// In the end, move piece down
p.moveDown();
		
And then we convert display to pretty string and send the tweet as mentioned before. And then interval waits for another 5 minutes, checks for counts, does action and post tweet, repeat forever.
At start of interval function you also have to check for death, and if they died then send death tweet and start game over.

That's it, the game is done! You can check how people suck at it via this link. Cya in another, uh,,,, lot of months...