Templates are dynamic expressions evaluated on the server before the page is sent to the players. In the browser, only the result of the expression for their specific team is shown. The template code itself is NOT visible, even in the page source code.
Classic applications of templates:
Templates can be written in regular editor mode or in Source mode in the task description, hint text, or bonus text. Templates can be nested.
To make it easier to write template syntax, you can copy and use a special spreadsheet: QEng Template Generation Google Sheet.
{% match line }:{
{%= 688 /*comment: lineup name*/}:{
Text for lineup with ID 688
%}
{%= 643 }:{
Text for lineup with ID 643
%}
{%= 653,655 }:{
Text for lineup with ID 653 or 655
%}
{%= ?}:{
Text for any other lineup
%}
%}
{% match team }:{
{%= 688 /*comment: team name*/}:{
Text for team with ID 688
%}
{%= 643 }:{
Text for team with ID 643
%}
{%= 653,655 }:{
Text for team with ID 653 or 655
%}
{%= ?}:{
Text for any other team
%}
%}
Useful for releasing codes or hints at a specific absolute time. To convert date/time, use Unix Time Converter.
{% unix_time 1639999296/*date in unix time*/, Opens, Left}:{
This text will be shown after the specified time.
%}
*(Replace `code_1` with the required code index, e.g. `code_5` for the fifth code)*
{% match code_1 }:{
{%= abc}:{
The answer "abc" is submitted for the first code.
%}
{%= bac, cba}:{
The answer "bac" or "cba" is submitted for the first code.
%}
{%= !no_answer!}:{
No correct answer has been submitted for the first code yet.
%}
{%= ?}:{
Some other answer synonym has been submitted.
%}
%}
{% match code_first_2 }:{
{%= abc}:{
The first answer submitted for this code was "abc".
%}
{%= !no_answer!}:{
The code has not been submitted yet.
%}
{%= ?}:{
Another answer was submitted.
%}
%}
Besides specific answers, a branch may hold:
!no_answer! — the correct answer has not been submitted yet.!any_answer! — any answer has been submitted. Unlike ?, it does not match when there is no answer yet. Handy when a code has synonyms and all you care about is that it is solved.? — any value, including !no_answer!. Put this branch last: everything listed after it is never checked.{% match bonus_3 }:{
{%= !any_answer!}:{
The bonus is solved — no matter which synonym was used.
%}
{%= !no_answer!}:{
The bonus is not solved yet.
%}
%}
Just like codes, you can check answers for bonuses:
bonus_1 / bonus_first_1 — last or first answer submitted for bonus 1 on the current task.gbonus_1 / gbonus_first_1 — last or first answer submitted for spanning (global) bonus 1. Spanning bonuses can be checked even on levels where they are not displayed.A template branch can hold not only text but also a{! code … !}marker: when the branch matches, the server enters the code for the team by itself. That is how templates build chains — see Server Actions.
A submitted synonym replaces the previous answer of a code or a bonus, and match sees the latest one. This keeps working after the bonus is already solved: the player submits another synonym of the same bonus, and the checked value changes.
bonus_first_1 holds the very first answer and never changes.Hence the technique: an object with any number of states is one bonus with a synonym per state. The number of states is not limited by the number of bonuses, and you can switch them as many times as you like, even in a loop.
{% match bonus_2 }:{
{%= lever_down}:{
The lever is down, the slab has slid aside.
%}
{%= ?}:{
The lever is up, the niche is shut.
%}
%}
Bonus 2 here has two synonyms — lever_down and lever_up. Whichever the player submits last is what the level shows.
Mind anything granted once: if an item or a hint is added by a script inside a branch, going back to another state takes it away. Keep one-off things outside the branches and branch only on what is meant to change.
To switch a state from a button use enterOnClick() only: enter() submits a code once and will not send the same one again, so the state gets stuck on the synonym submitted last. Applying an inventory item and typing an answer are not limited this way.
You can prepend who_ to answer checks to find out who in the team submitted the answer.
Possible values:
me — code was submitted by the current logged-in user.other — code was submitted by another team member.!no_answer! — not submitted yet.*(Anonymous players without registration are treated as the same user).*
{% match who_code_1 }:{
{%= me}:{
I submitted the first code.
%}
{%= other}:{
My teammate submitted the first code.
%}
{%= !no_answer!}:{
Nobody has submitted the code yet.
%}
%}
Prepend who_team_ to check which team solved a spanning/shared task:
Possible values:
me — my team solved the code.other — an opponent team solved the code.!no_answer! — not solved yet.{% match who_team_code_1 }:{
{%= me}:{
Our team solved this code first!
%}
{%= other}:{
Opponent team solved this code first.
%}
%}
{% match lang }:{
{%= ru}:{
Russian interface language selected.
%}
{%= uk }:{
Ukrainian interface language selected (uk).
%}
{%= en }:{
English interface language selected.
%}
{%= ?}:{
Some other language.
%}
%}
There is an easier way now - for translating text, links and pictures. A multilingual game is made with translations: Multilingual Game (Translations). The task stays one text, the translations live beside it, and the editor shows what is still untranslated. The template below keeps working and nothing has to be changed in existing games, and for a new game it stays the right choice where a language does not need a different version of the same text (that is exactly what translation is for) but a genuinely different structure: its own set of html blocks that other languages do not have, its own logic in the level script rather than just a different string in it, or branching by language combined with another template - by code, by team, by bonus. If you just need a translation - see translations above.
This template lets one game run in several languages at once: a player sees only their own branch, and the other translations never reach the page source.
Players pick the language in the site menu; it can also be set with a parameter in the page address — ?lang=de, ?lang=uk and so on. The choice is remembered in cookies. If the player has never chosen, the language is detected from their browser settings.
lang returns a two-letter language code. The current list of languages is always visible in the language menu on the site; at the time of writing it is de, en, es, et, fi, fr, it, lt, lv, nl, pl, ru, uk.
Things worth remembering:
? branch and put it last. It fires when the player's language has no branch of its own — otherwise such a player sees an empty task. It usually holds the text in the game's main language.{%= ru, uk}:{ … %}.
The easy way to check a translation is to open the game with an explicit language parameter — https://qeng.org/game/1234/?lang=lt. Look at the ? branch too: just pick a language that has no branch of its own.
To give the player a language switcher inside the task itself, put a plain select into the text and its handler into the Task script:
<select id="lang_select"> <option value="en">English (en)</option> <option value="uk">Українська (uk)</option> </select>
var select = document.getElementById('lang_select'); if (select) { // document.documentElement.lang is the CONTENT language, which gets // overridden if the game itself also has translations turned on (see // above). The interface language the player actually picked is more // reliably read from the lang cookie. var match = document.cookie.match(/(?:^|; )lang=([^;]*)/); select.value = match ? decodeURIComponent(match[1]) : document.documentElement.lang; select.onchange = function () { var url = new URL(window.location.href); url.searchParams.set('lang', this.value); window.location.href = url.toString(); }; }
How to create a product, place a payment button and test the whole thing without real money is described in Paid Content (Paywall). What follows here is only the template that closes off the paid content.
If your game uses paid levels or paid hints, you can check the purchase status:
product_KEY — where KEY is the product key from settings.Possible values:
yes — product purchased by the team.pending — payment started but not yet finalized.no — not purchased.{% match product_level_5 }:{
{%= yes}:{
The team purchased access to level 5.
%}
{%= pending}:{
Payment is pending. Please wait and refresh the page.
%}
{%= no}:{
Access closed. Price is $1. Click to purchase:
%pay_button level_5%
%}
%}
When a template sits in the middle of javascript code, the code editor complains about the {% %} syntax — for javascript it is a syntax error. To avoid that, the service lines of the template may be commented out with a double slash before evaluating the template, so the code inside the template becomes live.
. The engine strips those
A is optional, and the indent before is stripped only when it is immediately followed (spaces aside) by {% or %}. A space after — and a short template can be written on a single line:
is preserved.
<code JavaScript>
show_money();
{% match gbonus_141}:{
{%= some_code}:{
enter_silent('the whole game is done');
%}
{%= ?}:{
play_sound('limit');
%}
%}
</code>
A team that entered some_code into global bonus 141 gets:
<code JavaScript>
show_money();
enter_silent('the whole game is done');
</code>
Closing brackets may share a line — %} %}
// {% match team}:{ {%= 172}:{ var boss = true; %} {%= ?}:{ var boss = false; %} %}
Regular comments are not affected: in the line see {% match team}:{ {%= 172}:{ here %} %} is followed by text rather than by the start of a template, so the the ).
stays and the line remains a comment ( see here