Simon blogs

Started by Simon, October 18, 2015, 06:05:44 AM

Previous topic - Next topic

0 Members and 3 Guests are viewing this topic.

Simon

#375
No hidden hotkeys.
  • Every function gets a GUI button with the hotkey printed on it.
  • If it can't be a GUI button, e.g., directional select or priority invert, the function gets a tooltip as soon as it's useful.
  • Can you automate the function to reduce mental burden?
  • Esc is special. It's a common hidden key in games, and it's like the home button on smartphones.
Still, in singleplayer Lix, you don't even need Esc; you can nuke. The nuke has a GUI button with a printed hotkey.

No dialogs.
  • If you must display a message, display it without interrupting the control flow.
  • The classic essay on this rule is Death to the Dialog Box on Coding Horror, 2004.
  • Example: Level loading errors show in the Lix level preview screen. You can keep clicking levels.
  • Five years ago, I followed this to an extreme, removed the end-of-singleplayer dialog, and printed game outcomes into the level browser. Only when people wanted a next-level button, the browser would have become too crowded.
  • Dialogs break flow the most after you perform action/command X and usually get no dialog after X.
On hard-to-remove dialogs:
  • Menus (that we implement as dialogs) are okay when we consciously summon them for their unique content, e.g., the options menu.
  • The menu, if it is a modal dialog (i.e., you must finish dealing with it before you may continue your normal work), must still pull its weight. If we only have 1-2 small options, can we stick those into another existing screen?
  • I believe that dialogs are less annoying (but still annoying) when you expect a drastic context switch anyway and have no clear next goal.
Example: You return from game to the menu. I still want to avoid dialogs here, but they're less nasty here than elsewhere.

Example: You start a time-consuming computation with nontrivial results that you rarely need, e.g., mass replay verification from inside the GUI.

Counterexample: When you need the replay verification often, dialogs will get annoying. E.g., you maintain Lix and want to test all packs before each release. Run the replay verification from the command line:
$ lix --coverage replays/path/to/your/pack

Counterexample: After you save a level in the editor, you usually want to playtest it. This is a clear goal; you don't want strange dialogs to get in the way despite the drastic context switch between editing and playing. Thinking about this, this leads us to more design insight: The context switch between editing and playing a level shouldn't be drastic in the first place. You're working on a single level throughout, after all. IIRC, back in the day, GigaLem suggested a button in the editor to playtest immediately.

-- Simon

Simon

#376
Cultural Moss

  • namida supports different spritesheets.
  • WillLem draws the lemminas.
  • namida adds the laserer.

Who is responsible to draw the lasering lemmina? Certainly WillLem.

  • Simon writes the game physics.
  • geoo builds a level.
  • Simon merges the level into the main release.
  • Simon changes the physics, breaking the level's solution.

Is geoo still responsible to fix his level?

Or is Simon responsible to fix the level because Simon manages the level's releases? What happens if Simon doesn't know the solution and geoo is away on a month-long trip without internet? Should Simon watch geoo's intended solution from the replay collection and spoil the puzzle for himself?



  • DMA covers the blocks in Steel Works with moss.
  • namida and Simon conspire independently make the moss destructible.
  • DMA chooses not to register on Lemmings Forums to submit an updated level.

Who is responsible to fix Steel Works? What is even the best fix? Should the fix be in the game physics or in the level's moss placement? Oh, the popcorn!

  • You're a compiler writer.
  • You recognize these problems as real-life variants of the Expression Problem.
  • If you have an open class hierarchy and the codebase has subclasses everywhere, it's hard to add new pure virtual methods to the base.
  • If you have a closed class hierarchy with a visitor pattern, you can define visitors everywhere in the codebase, but no new subclasses.

This one is easy. You are responsible. If you run into the problem of adding new expressions often, your language is still young, and you're likely the only compiler writer. Thus, you are responsible to fix the hard-to-fix half of the problem.

  • You're a language designer for a popular language.
  • Other people write software in your language.
  • You want to change the syntax from "int foo()" to "auto foo() -> int".

This is also easy. You must maintain both styles to the end of ages. You push the burden of choice down to developer teams; they can pick a style for the codebase or accept a mix.



The hard variant of this problem is: You're designing an uncommon language and want to change the syntax. In the early days of Zig, you had to write "fn foo() -> i32" to declare a function that returns a signed 32-bit integer. The designers thought that "fn foo() i32" is better. Their Reasoning: foo is the function that maps into the type i32 and foo(), i.e., the call of the function, produces a single i32, but is not itself a mapping of anything into the type i32. Nowadays, it's a hard error to write "fn foo() -> i32". The Zig compiler tells you that it expects a return type, not "->". Thus, early during Zig development, users had take off every zig arrow in all (= the few) of their codebases.

How to handle cultural moss?

Have as few problems of this kind as possible. If you have an ecosystem, you'll probably have at least one such problem, otherwise people can't contribute creatively. Decide if you even want to build an ecosystem in the first place. Maybe your players can only solve levels, not build any? Maybe you don't need a core component (e.g., a computer game) that needs maintenance in the first place?

Find the axis that grows more often (levels) and make it easy to add to this axis. Accept that even small breaking changes the other axis (physics) will be hard.

If you have more than one such problem, avoid overlap. You already have two axes (levels and physics) whose span is hard to fill. You don't want a third axis. E.g., namida absolutely forbids to change existing tiles. In Lix, I allow tile changes in the centrally released tilesets, but I treat them like physics changes, thereby merging two axes (tiles and physics) into one.

Fail noisily. Cover your levels with solution replays. Test the replays after every change to either axis. In software, even though the visitor pattern in classical OOP languages brings boilerplate, it breaks clearly at compile time when a case is missing.

-- Simon