Starting Something of Your Own

How to get from a blank file to a program that works, on an idea nobody handed you.

Everything up to now came with a program already in it. The introduction gave you examples to run, and Building Your Own Keep gave you a whole world to take apart and extend. Both are real programming and neither is the thing that scares people. What scares people is the empty file, and it is a completely different feeling: nothing to react to, no next line suggested, and a quiet sense that everyone else knows where to start.

They do not. The blank file is hard for everybody, including people who have been doing this for decades, and it is hard for a reason that has nothing to do with how much lux you know. An empty file asks you to decide everything at once — what it does, how it is shaped, where it begins — and no one can hold all of that in their head at the same time.

So here is the thing that actually changes it, and it is not a trick. Nobody writes a program. They write a tiny program that works, and then change it, over and over, until it is the program they wanted. Every piece of software you have ever used got there that way. The finished thing looks like it was planned in one go because you are seeing the last version and not the ninety before it.

This page is the method for that, walked on a real example built from nothing in front of you. It assumes you can read a little lux; if a line stops making sense, lux learn in your terminal and the introduction on the web both explain every piece of it.

Make it smaller than you want to

Almost every first project fails in the same way, and it is not a coding failure. It is picking something too big, working for three evenings, ending up with a half-built thing that does not run, and concluding you are not cut out for this. The idea was fine. The size was wrong.

There is a test for the right size and it takes ten seconds. Say what the program does in one plain sentence, without the word "and."

"It quizzes me on capital cities" passes. "It quizzes me on capital cities and keeps my scores between sessions and lets my friend log in and has a menu" is four programs wearing a coat, and if you start it you will still be on the menu next week. The word "and" is where projects go to die.

The bigger idea is not lost. It is what you build third. The thing that makes the big version possible is having built two small ones first, because the second one is dramatically easier than the first and by the third you have stopped needing pages like this. Starting small is not lowering your ambition. It is the only route to the ambitious thing that actually arrives.

The example on this page is a set of flashcards. You get asked a question, you type an answer, it tells you whether you were right, and at the end it tells you how many you got. One sentence, no "and" doing any real work. Small enough to finish in a sitting, and — this matters more than it sounds — actually useful afterwards, and that is what makes you come back and improve it.

Say what happens before you write it

Before any code at all, write down what running the program looks like. Not how it works — what you would see. What it prints, what you type, what it prints back.

For the flashcards, that is:

FLASHCARDS

capital of France? Paris
Right.
capital of Japan? Tokyo
Right.
capital of Peru? Cusco
No — it's Lima.

You got 2 out of 3 right.

That took a minute and it is the highest-value minute in the whole project, because it is the cheapest possible place to discover that your idea is vague. Writing that out, you have already answered several questions you did not know were questions: does it tell you the right answer when you get it wrong (yes), does it stop after one round (yes), does it ask them in order (yes). Each of those could have been an argument with yourself at line thirty.

Changing a sentence costs nothing. Changing forty lines of code costs an evening. Do as much of your deciding as you can while it is still sentences.

Put it at the top of the file as a comment. It stays useful the whole time you are building — it is what you check the real output against, and it is how you know when you are done.

Work out what it has to remember

This is the one idea from the keep that carries to every program you will ever write, so it is worth pulling out of the dungeon and saying plainly. Before you write a single function, ask: what does this program need to hold onto?

Everything else follows from the answer, because a program is mostly some information plus the things that change it. Get the information right and the functions almost suggest themselves. Get it wrong and every function you write will be fighting the shape.

The answer usually lands in one of four places. If the program does not need to remember anything, it is a straight run from top to bottom and you are lucky. If it is one thing, that is a variable. If it is several things that belong together and travel together, that is a struct. If it is many of the same thing, that is an array.

The flashcards need two. A card is a question and its answer, which belong together, so that is a struct. There are several cards, so that is an array of them. And it needs to count how many you got right — one number that changes, so that is a variable.

struct Card {
    front: string
    back: string
}

Two fields and a name. That is the design, and it took longer to read this section than it will take you to decide the same thing for your own program.

In your terminal: lux learn structs and lux learn arrays. The slower version: Structs and Arrays in the introduction. And the long version of why the state comes first is The world is a value, in the keep guide.

Get something running in one line

Now open the file, and resist every instinct that says to write the program. Write this:

print("FLASHCARDS")

Run it:

FLASHCARDS

That looks like a joke and it is the most important step on this page. You now have a working program. It does almost nothing, but it runs, and that means from here on you are never building a program — you are only changing one that already works. Those are two completely different activities and they feel completely different. Changing something that works is comfortable. Staring at a half-built thing that has never once run is where people quit.

From that follows the one rule that will save you more time than anything else here: never write two things before running it. Add a piece, run it, look at what happened. Add the next piece. When something breaks — and it will — you know exactly which change broke it, because there was only one. That is the reason this works.

Add one piece, run it, add the next

Here is the whole build. Four steps, and every one of them runs.

Step one: ask one question

Not all the cards. One question, with the answer written straight into the code, because the point right now is to find out whether asking and checking works at all.

print("FLASHCARDS")
print("")

let answer = input("capital of France? ")

if answer == "Paris" {
    print("Right.")
} else {
    print("No — it's Paris.")
}
FLASHCARDS

capital of France? Paris
Right.

Run it again and get it wrong on purpose, because half of testing is checking that the unhappy path works too:

capital of France? Berlin
No — it's Paris.

Both branches work. The core of the program is proved, in nine lines, before any of the structure exists.

Step two: many cards

Now the struct earns its place. The question and answer come out of the code and into data, and one loop does what the hard-coded version did once.

struct Card {
    front: string
    back: string
}

let cards = [
    Card(front: "capital of France", back: "Paris"),
    Card(front: "capital of Japan", back: "Tokyo"),
    Card(front: "capital of Peru", back: "Lima"),
]

print("FLASHCARDS")
print("")

for c in cards {
    let answer = input(c.front + "? ")
    if answer == c.back {
        print("Right.")
    } else {
        print("No — it's " + c.back + ".")
    }
}
FLASHCARDS

capital of France? Paris
Right.
capital of Japan? Kyoto
No — it's Tokyo.
capital of Peru? Lima
Right.

Look at what the loop bought. Adding a fourth card is now one line in the list and no change to the logic at all, and it would be the same one line if there were four hundred. That is the trade you are making every time you move something out of the code and into data, and it is most of what "well written" means in practice.

Step three: keep score

Three new lines, and here is the whole file with them in — var right = 0 before the loop, right = right + 1 inside the successful branch, and the report at the end.

struct Card {
    front: string
    back: string
}

let cards = [
    Card(front: "capital of France", back: "Paris"),
    Card(front: "capital of Japan", back: "Tokyo"),
    Card(front: "capital of Peru", back: "Lima"),
]

print("FLASHCARDS")
print("")

var right = 0

for c in cards {
    let answer = input(c.front + "? ")
    if answer == c.back {
        print("Right.")
        right = right + 1
    } else {
        print("No — it's " + c.back + ".")
    }
}

print("")
print("You got", right, "out of", length(cards), "right.")
FLASHCARDS

capital of France? Paris
Right.
capital of Japan? Kyoto
No — it's Tokyo.
capital of Peru? Lima
Right.

You got 2 out of 3 right.

That is the program from the comment at the top of the file, finished. Go back and read that comment now, and check it line against line — this is what it was for, and it is how you know you are done rather than merely tired.

Notice what did not happen anywhere in that build. At no point was there a broken program sitting on the desk. Every single step ended with something that ran, which meant every problem that came up was findable in the handful of lines that had just changed. That is not because the example was chosen to be easy. It is what the method buys, and it works exactly the same way on something ten times the size.

Notice too that the shape is the keep's shape. Some state that matters, a loop that goes round once per turn, and a decision inside it. You have seen this before, and you will see it again in almost everything you write, because it is not a text-adventure pattern. It is what a program mostly is.

When lux says no

Run the finished program and type paris in lower case. It marks you wrong.

capital of France? paris
No — it's Paris.

Nothing is broken. "paris" and "Paris" are genuinely two different strings and lux answered the question you asked correctly. It is just not the question you meant, and this is the first improvement your program is asking for. It is also the exact moment a project becomes yours, because nobody told you to fix it — you played your own thing and it annoyed you.

You will hit a second kind of wall too, and it feels different. Suppose you want to shuffle the cards so they are not always in the same order:

error: unknown function `random`
note: define it with `func`, or use a built-in: print, eprint, string, int,
      float, length, contains, replace, split, input, readLine, readFile,
      writeFile, args, run, parseInt, parseFloat

That is not a mistake you made. lux has no random numbers, no way to turn a word into lower case, no dictionary type, and no way to spread a program across several files. Those are the walls you are most likely to walk into on a second or third project, and there is nothing you can type that will get you past them.

Here is the part worth carrying away. Wanting one of those is a good sign, not a bad one. It means your program grew big enough to need something a small language does not carry — which is not a thing you can fake and not a thing you can rush. Some of those gaps are deliberate, left out so that you feel the need before you meet the feature. Some are just lux being small. Either way, the answer is never to give up on the idea.

Sometimes you route around it, and routing around a limit is a real skill. The paris problem is one you can route around today, with nothing you have not already met. Give the card a second answer it will also accept, and check both:

struct Card {
    front: string
    back: string
    also: string
}

let cards = [
    Card(front: "capital of France", back: "Paris", also: "paris"),
]

for c in cards {
    let answer = input(c.front + "? ")
    if answer == c.back || answer == c.also {
        print("Right.")
    } else {
        print("No — it's " + c.back + ".")
    }
}

It is not elegant and it does not scale past a spelling or two. That is the right amount of annoying — enough to get today's game working, not so comfortable that you stop wanting the real thing. Sometimes you file the idea and build something else first. And eventually one of those walls will be the one that does not move, and that is the day When lux Feels Small is written for.

If you want the full account of what lux leaves out and why — including which absences are on purpose and which are simply missing — What lux Leaves Out is written for someone who already programs, but the list itself is readable from here.

What to build

Not a list to work through. Ideas at roughly the right size, to steal from or ignore, chosen because each one needs a slightly different shape and none of them needs anything lux does not have.

A practice drill for something you are actually learning right now — the flashcards with your own cards in it, a perfectly good place to start. A tally that asks how your day went on a few counts and prints the total. A calculator for a thing you work out often and always by hand — a recipe scaled to a different number of people, or how long until a date you care about. A quiz you write for somebody else, more fun than it sounds because you get to be unfair. A tracker with a small table at the end, for a run or a practice session or a game. Or a keep of your own from scratch rather than by editing the one lux gave you, harder than it looks and more instructive than any of the exercises.

Pick the one you would use. Not the most impressive one — the one where the finished version would sit on your machine and get opened again. That is the difference between a project you complete and an exercise you abandon, and it is very nearly the only thing that matters in choosing.

Then write one sentence about it, sketch what running it looks like, work out what it has to remember, and get one line printing. The rest is the same loop as everything above: add one piece, run it, add the next.

When it breaks, and it will: When It Doesn't Work is how to find out why. In your terminal, lux magic keeps a working example of most of these shapes one command away.