GOTO for flow control is not that bad, it's just a tail call after all. The real damage is things like having everything be global variables. But part of that was a technical limitation, as things like "proper" call stacks and reentrant code as the default still had plenty of overhead, and even FORTRAN didn't support them.
I'm not sure what you have in mind but in the 1975 dialects of BASIC the code was something like
10 REM Grab input
20 GOSUB 1000
30 IF VALUE=BAR THEN GOTO 100
40 IF VALUE=BAZ THEN GOTO 200
50 IF VALUE=BLA THEN GOTO 300
60 GOTO 20
100 REM Bar stuff
110 blahblah
199 RESUME
200 REM Baz stuff
210 blahblah
299 RESUME
300 REM Bla stuff
310 blahblah
399 RESUME
1000 REM Code that grabs input
1010 blahblah
1100 RESUME
Note that this is a bit more readable than what people worked with since now you have syntax highlighting.
I'm not sure where tail calls enter in the picture... there wasn't even support for functions, at best you had GOSUB/RESUME which was essentially assembly-like CALL/RET.
I don't think that example you've posted is right (the grab input one, not the game on Github).
Firstly, RESUME was to continue execution after an error (eg ON ERROR RESUME NEXT). I think RETURN is what you might have meant?
Secondly, you cannot RESUME/RETURN from a GOTO. I think you meant GOSUB; which was a crude approximation of subroutines. BASIC did also have GOTO as well though.
Yes you are right, i meant GOSUB/RETURN. Though instead of "RESUME" (which should have been RETURN) most programs would use GOTO 20 (in an opposite expression of what Dijkstra wrote about, i accidentally used a 'function call' idiom instead of a spaghetti 'jump wherever' one :-P).
> I'm not sure where tail calls enter in the picture...
GOTO essentially involves setting the continuation of your program to something other than the following instruction(s). That's what a tail call does. No "functions" involved except as a high-level description.
Goto has its place. It's not evil, but should be used judiciously. After all, C family constructs such as break, continue are glorified/specialized, albeit scoped and unlabelled gotos.
One of the places I like gotos is when I need to break out of a nested loop. A goto to outside of the outer loop is far more obvious than a flag and a conditional to break from the parent nested loop. It also generates better machine code and uses fewer registers, at least in debug mode.
Sure, goto can be abused, just like every other language construct. They have their time and place, though.
The issue was not the presence of goto itself, but the fact that is was one of the main tools for flow control, while as you say it should be used sparingly.
It is like having an army of soldiers armed with a David Crockett[1] instead of a regular rifle. Things may get ugly fast.