Home > Root > Log book > Managing conditions

Managing conditions

Saturday 30 November 2024, by Mathieu Brèthes

All the versions of this article: [Deutsch] [English] [français]

Well, it looks like this blog is becoming project notes. Should I be worried? Maybe not. Anyway, I have gone forward with my Dia to RenPY translator, called DiaVN. But translating from one paradigm to another can lead to issues. Today, I am going to discuss how to handle conditions.

To understand, let’s go back to the real basics. What is a program if not a sequence of steps, each modifying the state of the computer on which it is executed? Let’s imagine your computer has feet, and you want to ask it to walk for a mile. You are going to write a program with one thousand "take a step forward" instructions, like so:

take a step
take a step
take a step
take a step
take a step
take a step
take a step
take a step
take a step
... // 990 lines later...
take a step
phew

What if we could reuse our existing code instead of having to rewrite the same line over and over again? The concept of a "loop" allows just to do this. Let’s add the loop:

take a step
go back to the previous line
phew

Now we are reaching today’s problem: with this loop above, how do you tell the computer to stop after the mile is trodden? Here, the computer will gleefully walk to infinity (or at least as far as its power cable can take him). it will never say "phew".

So we need to add a second element to our program: a condition. This is a special statement which can check the state of the computer, and change the flow depending on this state. Here is the final program with a loop and a condition:

take a step
did we walk a mile yet?
no : go back to the first line
yes : phew

Wohoo, we just invented Basic! *cough*

So what does DiaVN do? It allows to represent programs using diagrams instead of lines of text. Or to be more precise, each element of the diagram represents an action (in a way, like a line). Each arrow between two elements is the program flow. The idea is to be able to create programs (here, adventure games) less linera than the ones we are commanded to do when we use text to program (see A Visual Novel using a graph tool as a development tool?). So today’s issue is about representing conditions in DiaVN.

Let’s dig in. DiaVN generates RenPY compatible code, and RenPY is more or less Python. The condition statement in Python is very broad. In Dia, I represent a condition by a losange, and each possible solution (such as the yes/no above) by arrows leaving the losange and pointing towards another element. This way, the flow can "branch".

I could decide to let the user freely put whatever she wants in the condition element, then freely label the branches, and pray that the translator copy-pastes all this into viable RenPY code. This is the "I don’t give a fsck" approach, where DiaVN is just used as a flow-management tool, and each element is simply a wrapper around unchecked RenPY/Python code.

My first issue here is that I hope to use DiaVN to someday generate code for something else than RenPY (ultimately a game for the Neo Geo Pocket Color, so assembly code). If I just wrap RenPY code, I will not be able to easily port my game on the two platforms - I would also need to write a RenPY to NGPC converter.

The second issue is that using diaVN as a wrapper around RenPY code will make it very hard to debug the game when issues will inevitably arise. The RenPY interpreter will indicate errors to line numbers corresponding to code automatically generated by DiaVN, and for the user, it will then be complicated to read the automated code and figure out which element from the diagram is matching, specially if the project becomes bigger. And which elements are the more likely to generate bugs? Well, the ones where the RenPY code is in reality Python code, i.e. the conditions.

So, I have chosen to put constraints on how conditions can work with DiaVN. The user will only have 3 possibilities:

  1. she can compare a variable (contained in the condition element) to an immediate value (number or string), or another variable. When no comparators are specified, each value is compared by an implicit "equal" sign
  2. the same method but each compared element is precedent by a comparator (< > <= >= == !=), this to allow to check, for example, ranges.
  3. the element can contain a Python expression that can return True or False
Diagram showing the different condition systems supported by DiaVN

Noted that the third possibility leaves a lot of opening to the user to make a Python mess (so a warning should be yielded).

The problem of lists

All this is good, but there is a problem in this problem. In many programming languages, such as Python, and RenPY by extension, we do not "know" in advance what is in a variable. Is it an integer? A string? A... List?

Python leaves the user with the responsibility of not making mistakes in his program, and will sometimes not generate errors when incompatible types are compared (for example comparing the integer 1 with the string containing a "1" is allowed, but will return false).

This we can still manage. But a problem arises when we need to change the symbol used for comparison depending on the element type. This is what happens with lists.

So a list is an element which contains a sequence of other elements (each may also be a list). When a user compares a variable with a list, she usually wants to know if the value contained in the variable, is somewhere in the list. Or in our case, we need to use a special operator for this in Python, the "in" operator. And the only way to understand what the user wants to do, is to know in advance the type of the variable...

So we could solve this either by:

  • forbidding the use of lists

or

  • give DiaVN some prescience over what variables contain.

In 2024 it is maybe a little bit shameful to forbid using lists. So, we will need to introduce in DiaVN either the type declaration of variables, or a special keyword to indicate, at comparison time, that the element is a list. We could also leverage Python’s power, compare element types at run-time, then execute the correct condition depending on the situation. But this would, I guess, cost a lot of time, and make the generated code even more difficult to read.

Since I am a bit old-fashioned, I will opt for the first choice. I think I will ask for the variable names to start with:

  • i_ : integer number
  • f_ : floating point number
  • s_ : string
  • l_ : list

And the equivalent immediate values:

  • integer : 42
  • float : 0.1 (to represent an integer as a float, 1.0)
  • String : "hello, world"
  • list : [42, -63, 28, 12] (here, a list of integers)

But, what do we do with mixed-type lists, lists containing variables, lists containing other lists...?

Let’s for now acknowledge the following limit: a list is a list of elements of the same type, and it can’t contain other lists.

Well, what a day.