After receiving an invite for the GitHub Copilot Technical Preview I decided to try out the feature by building a browser-based Tetris-clone. This post will look at how Copilot offers suggestions while building software.

The completed result is below, and you can see the source here.

Use arrow keys + space bar

Approach

I’ll start from a blank slate and am looking to build a working app as quickly as possible. I won’t look to optimise the code for performance - rather I’m looking at a first pass that feels similar to the famous block game. I’ll use TypeScript for code and pixi.js for rendering, and hopefully Copilot will write a bunch of the code for me.

I install and authorise the vscode extension for Copilot, create a new vite project, and open the IDE:

npm init vite@latest tetris --template vanilla-ts
cd tetris
npm i pixi.js
code .

From the scaffolded main.ts that vite creates, I remove the app.innerHTML setter, and rename #app to #pixi. My starting point looks like this:

import "./style.css";

const element = document.querySelector<HTMLDivElement>("#pixi")!;

The First Suggestions

In the screenshots that follow, grey text is a suggestion from GitHub Copilot. I type const and immediately Copilot gives me the first suggestion:

Copilot suggestion: New pixi application

This suggestion is impressive for a number of reasons:

  • It’s correct - I was going to write that
  • The file does not yet have imports for pixi.js
  • The correct module alias is suggested: PIXI
  • The correct class name is suggested: Application

Hitting tab accepts the suggestion. I know I want pixi to resize based on its parent element, so intellisense gives me resizeTo as I type it but upon selecting it I get another suggestion from Copilot:

Copilot suggestion: resizeTo

This is also correct, and, references the existing variable. Hitting tab to accept the suggestion completes the line, but this is not executable code because an import is still required. I move to the top of the file, type import * as PIXI and another correct suggestion is offered:

Copilot suggestion: import pixi

Pondering whether Copilot was clever enough to inspect package.json or node_modules I tried the above steps again on a new repo without installing pixi. I get the same correct suggestions, and intellisense on the PIXI.Application options, even though the package is not installed - the screenshot below has a red squiggly under pixi.js because it’s not installed.

I go to the bottom of the file, hit enter, and again get a correct suggestion:

Copilot suggestion: append child

While I type, partial lines, and sometimes entire lines, are constantly suggested with striking accuracy. It continually uses and references what I’ve already written and appears to know as much pixi syntax as I do. Were I a pixi beginner, suggestions like app.screen.width introduce me to API that otherwise can take some time to search for and learn. So far, I’m quite impressed.

Copilot suggestion: PIXI.utils

Copilot suggestion: fillRect

Tetrominos

The player pieces in Tetris are called Tetrominos, so I’ll create an abstract class that represents their colour and shape and create a concrete class for each of the seven block types I, O, T, S, Z, J, and L. I create a new file, type the name of the class, and I get my first nonsense suggestion:

Copilot suggestion: Tetromino

At this point my pessimism has me expecting a lot more of these. However, when I discard the suggestion and start typing what one of the concrete implementations might look like, I get a startling result where each of the concrete classes is correctly suggested at once:

Copilot suggestion: Concrete classes

My intention is to have a static factory method on the base class that will return random Tetrominos. I type static and it turns out Copilot feels the same way I do:

Copilot suggestion: static

The next suggestion implements the entire method for me!

Copilot suggestion: getRandomTetromino

Each of the concrete classes need to have their block layout specified in the constructor. Copilot was able to suggest correct block layouts for each of the seven Tetrominos, however this needed tweaking afterwards as some used -1 and others 0 as the lowest y value.

Copilot suggestion: Concrete classes

On the base class, moveLeft, moveRight, and moveDown were correctly named and implemented via a sequence of pressing Enter followed by Tab:

Copilot suggestion: Class functions

Although I took a different approach later, I originally implemented a TetrominoView class responsible for drawing Tetrominos. Copilot suggested a constructor with using Parameter Properties - a feature I was unaware that TypeScript had. It additionally suggested a very accurate draw function. The only part I needed to change was to translate the sizes to pixels:

Copilot suggestion: TetrominoView

Writing Tests

With Tetrominos being created and drawn, I want to implement the rotate method. Geometry is not as brainless as bashing keys so I’ll write tests to help with this feature. I added jest to the project and started writing the first test. Copilot suggested an accurate, but otherwise low-value test:

Copilot suggestion: First test

My intention was to have Copilot write rotate for me, but initial suggestions were verbose and inaccurate, and admittedly it took me too long to remember you don’t need sin/cos when you’re rotating 90 degrees. Eventually, with a comment added to help suggestions, I got a concise result:

Copilot suggestion: rotate

Test writing suggestions weren’t great until I had a bit of repetition in the file, after which it could suggest accurate tests even if some of them missed a closing bracket:

Copilot suggestion: test

Copilot suggestion: test from name

Listening For Keys

After typing window.addEventListener it correctly suggested the event:

Copilot suggestion: add listener

And after accepting the suggestion, correctly suggested the implementation for arrow keys:

Copilot suggestion: implement listener

Moving my cursor to the bottom of the implementation, it then suggested adding game.drop(), a feature I had not thought of yet! Note the red squiggles on methods that don’t exist yet - they are correct, but yet to be implemented:

Copilot suggestion: game.drop()

When adding the drop method all I was certain of was that I’d add a new block at the end. So I added that, moved the cursor above and Copilot suggested the rest of the implementation correctly one line at a time:

Copilot suggestion: Implementing game.drop()

Suggestion Accuracy

After spending around eight hours building the game and messing around with Copilot I can comment on both its strengths and weaknesses. Ultimately it comes down to how frequent and accurate its suggestions are. Most social media screenshots I’ve seen are of nonsense suggestions, but I only had one or two occasions when that occurred during development, and it only takes a second to laugh move on.

When suggesting full functions I found it could miss a bracket - especially in tests. But, adding a single bracket after accepting a suggestion is still quicker than typing the suggestion myself. It will suggest modules that are not yet imported and, accepting the suggestion will not add an import - however a CTRL+. afterwards can Quickfix this. It doesn’t offer suggestions during multi-cursor activities. I use prettier to formatOnSave and doing so sometimes has Copilot suggest duplicate lines that already exist but have been formatted across lines. It also gets whitespace wrong a bunch, but formatOnSave fixes that anyway.

The frequent and, sometimes disturbingly, accurate partial and full-line suggestions are amazing time savers. The discoverability of new API and language features while typing code means you can learn like you’re pair programming. The occasional full function implementation is just icing on an already delicious cake.

Importantly, suggestions don’t rely on existing code to be syntactically correct. The code doesn’t have to build before accurate suggestions are available. I don’t need the correct imports to be present. This means the tool can immediately slip into my way of coding - add some lines here, move up or down, tweak this, write half a line here, it doesn’t matter - the suggestions keep coming through.

Conclusion

Overall I’m very impressed with Copilot and quite happy to include it as part of my development toolset. I chose a project for this post that I presume has plenty of implementations across public repos, and Copilot’s knowledge of Tetris does appear to validate this presumption. I’m looking forward to seeing how it will perform across other project types and codebases in the future.