Here you can load files, from one of the examples listed or from a file on your device. You can also download the current game to a file on your device.
Navigate to the next tabs to read the instructions for how to play, to view and edit the game rules, and to enter your strategy.
Load game
Upload game from file
Download current game
General gameplay
This game is played by writing a logic program that expresses your strategy. You are the player. The rules of the game are also expressed by a logic program. For example, what is the effect of moves? What do opponents (if any) do? When is the game won or lost?
In each level, the player controls some elements, and nature controls the rest. The game is played in discrete time steps. At each time step, the player performs some action(s). Based on these actions, nature determines the next state of the game.
Not everything in the game may be observable to the player. The player can keep (and update) some things in their memory as the game progresses.
The various elements and features of the game are explained in detail below.
How are levels generated?
The generation of levels is expressed by a logic program (labeled level generation). An answer set for this program encodes the level using atoms with predicate names at, setting, and decorate. Atoms over the predicate at specify the (initial) state of the world in the level. Atoms over the predicate setting specify features of the level that remain fixed over the course of the game. Atoms over the predicate decorate specify how the level is visualized (for example, which icons are used for items, and the background colors of cells). See How is the game visualized? below. Atoms over debug are also shown in the level state (in the level editor), but they are not part of the game. Other atoms in the answer set are ignored.
The level generation program may contain substrings of the form RANDINT(l,u), where l and u are integers (possibly negative, and possibly with spaces around them, as in RANDINT(-2, 2)). In the preprocessing of the program, these are replaced by a random integer between (and including) l and u. This gives an easy way to include randomness in the level generation.
The program labeled auxiliary program is added to the level generation program (as well as to the other programs). The aim of this auxiliary program is to define helping predicates that can be used in multiple parts of the game.
For example, the following program specifies a game that is played on a 3x3 grid, with initial locations for an element player and an element flag, and encodes some information about how to visualize the game.
% The game is played on a 3x3 grid setting(grid_width(3)). setting(grid_height(3)). % Time bound: 4 steps setting(time_bound(4)). % The player is placed in the top-left cell at(1,1,player). % The flag is placed randomly in the bottom row at(3,RANDINT(1,3),flag). % Visualization instructions decorate(game_type,grid). decorate(label,flag,font_f024). decorate(label,player,font_f007).
What can the player observe?
The logic program labeled visibility encodes what parts of the game are visible (or observable) to the player at each time step. For this, the predicate name observe is used. To this program are added the current state of the game (in the form of atoms over at), and the level settings (in the form of atoms over setting). An answer set of the resulting program, restricted to atoms over the predicate observe, constitutes what is observable to the player. These atoms, together with the level settings, are passed on to the player's program. Atoms over at are never passed on to the player directly: the player only knows what is made observable here.
For example, the following visibility program captures that every true atom at(R,C,O) is observable.
% Everything is observable observe(at(R,C,O)) :- at(R,C,O).
How does the game progress?
The logic program labeled nature's moves expresses how the game progresses from one state to the next. To this program are added the current state of the game (in the form of atoms over at), the move(s) of the player (in the form of atoms over do), and the level settings (in the form of atoms over setting). An answer set of the resulting program, restricted to atoms over the predicate next, constitutes the next state of the game, where these atoms are translated to use the predicate at instead of next.
Nature can also wipe the player's memory. If the answer set contains the atom wipe_player_memory, the player starts the next time step with an empty memory. This also erases anything that the player chose to remember in the current time step. For example, the following rule expresses that the player's memory is wiped when the player steps onto an item amnesia.
wipe_player_memory :- next(R,C,player), at(R,C,amnesia).
This program may contain substrings of the form RANDINT(l,u), where l and u are integers (possibly negative, and possibly with spaces around them, as in RANDINT(-2, 2)). In the preprocessing of the program, these are replaced by a random integer between (and including) l and u. This happens anew at every time step. This gives an easy way to include randomness in nature's moves.
Only atoms over next carry over to the next state of the game; any other atoms derived by this program are not kept (except wipe_player_memory). In particular, an object for which no next atom is derived disappears from the game. So objects that stay where they are need a rule such as next(R,C,flag) :- at(R,C,flag). If the answer set contains no atoms over next at all, the next state of the game is empty (all objects disappear), and time still advances.
If the combined program has no answer sets, the state of the game does not change (but time does advance), and the player's memory is not wiped.
For example, the following program expresses that the flag moves to a randomly selected location that is in the same column as where it was in the previous state.
% The flag moves randomly inside its column next(RANDINT(1,3),C,flag) :- at(R,C,flag).
When is a game won or lost?
At each time step, the logic program labeled winning conditions determines whether the player wins or loses the game, or whether it continues. To this program are added the current state of the game (in the form of atoms over at), the level settings (in the form of atoms over setting), and an atom current_time(t), where t is replaced by the integer value specifying the number of time steps that have passed. An answer set of the resulting program is taken. If this answer set contains any atoms over lose, the player loses the game. Otherwise, if this answer set contains any atoms over win, the player wins. Otherwise, the game continues. So if both win and lose are derived, the player loses.
This check is done at the start of each time step, before the player moves. So the initial state of the level is checked as well. If the program has no answer sets, the game continues.
For example, the following program expresses that the player wins if it reaches the flag, and that it loses if time runs out.
% Goal: reach the flag win :- at(R,C,player), at(R,C,flag). % You lose if time runs out.. lose(timeout) :- current_time(T), setting(time_bound(S)), T >= S.
How to instruct the player?
At each time step, the logic program labeled player strategy specifies what move(s) the player performs at that time step, and how the player's memory is updated. To this program are added what the player can observe (in the form of atoms over observe, as computed by the visibility program; see What can the player observe? above), the level settings (in the form of atoms over setting), the player's memory (in the form of atoms over memory), an atom current_time(t), where t is the number of time steps that have passed, and the auxiliary program. The player does not get the state of the game itself (the atoms over at). An answer set A of the resulting program is taken. This answer set restricted to atoms over do constitutes the moves that the player performs. This answer set restricted to atoms over remember and forget specifies how the player's memory changes.
The player's memory in the next time step consists of (i) all atoms memory(M) such that forget(M) does not occur in A, and (ii) all atoms memory(M) such that remember(M) occurs in A. So if both remember(M) and forget(M) occur in A, the atom memory(M) is kept. Atoms memory(M) that the program derives itself are not stored; only remember stores things. The only exception is when nature wipes the player's memory (see How does the game progress? above): then the player's memory in the next time step is empty.
If the combined program has no answer sets, the player performs no moves and its memory does not change.
The player strategy program may contain substrings of the form RANDINT(l,u), where l and u are integers (possibly negative, and possibly with spaces around them, as in RANDINT(-2, 2)). In the preprocessing of the program, these are replaced by a random integer between (and including) l and u. This happens anew at every time step. This gives an easy way to include randomness in the player's strategy.
RANDINT can only be used in the level generation, nature's moves and player strategy programs. It is not replaced in the auxiliary, visibility and winning conditions programs.
For example, the following program expresses that the player performs the action wait, and that the memory counter counter(T) is increased by one.
% Wait do(wait). % Increase counter in memory forget(counter(T)) :- memory(counter(T)). remember(counter(T+1)) :- memory(counter(T)).
Creating your own games
You can either load and play an existing game. Loading games can be done in the Control tab. Or you can design your own game. In order to do so, specify the programs under the Game rules tab.
Note: in order to edit the auxiliary, level generation and visibility programs, you need to unlock these for editing. This will remove the level that is currently in memory, to ensure that the level in memory is based on the auxiliary, level generation and visibility programs.
Tips for writing programs
- Only the first answer set that is found is used. Randomness comes from RANDINT (or from #heuristic statements that use it), not from the choice of answer set.
- The visibility, nature's moves and winning conditions programs also get the whole history of the game, in the form of atoms at_time(T,R,C,O) (object O was in cell R,C at time T), together with atoms state_time(T) for every time T so far. The atoms over at are the ones for the latest time.
- The atom current_time(T) is available in the visibility, nature's moves, winning conditions and player strategy programs (and so also in the auxiliary program, when it is used together with one of these).
- After 500 time steps, the game is stopped with a timeout, to avoid games that go on forever. This counts as a loss.
- If a program has no answer sets, or contains an error (such as a syntax error), this is reported in the game output in the Debug tab, together with what the game does instead. Line numbers in error messages refer to the programs as glued together, not to the program on its own.
How is the game visualized?
Currently, only games played on a rectangular grid can be visualized. In order for this to work, the level settings must include facts setting(grid_width(w)) and setting(grid_height(h)) for some positive integers w and h.
Items on the grid may be visualized with an icon in the Font Awesome font (version 7, free icons in the solid and brands styles) as follows. To depict an item named obj in the logic programs by an icon with unicode representation fXXX (shown on the icon's page; newer icons may start with e, such as e4c0), add the following fact to the level generation program: decorate(label,obj,font_fXXX).
For example, the following line expresses that the item named bug is depicted with a bug icon.
decorate(label,bug,font_f188).
The background color of cells can be set with atoms of the form decorate(bgcolor,R,C,Color), which color the cell R,C. The color Color can be a CSS color name written in lowercase (such as lightgreen), or a hexadecimal color code prefixed by hex_ (such as hex_ff8800 for #ff8800, or hex_aac for #aac). Each cell should get at most one background color.
Background colors given in the level generation program stay the same throughout the game. For example, the following line colors the bottom row of a 3x3 grid light green.
decorate(bgcolor,3,1..3,lightgreen).
Background colors can also be derived in the visibility program. There, they are computed anew at every step, based on the current state of the game, and they can depend on what the player observes. A background color takes precedence over the gray color of cells that are covered by fog (observe(fog(R,C))). Like all atoms over decorate in the visibility program, these atoms are not passed on to the player. For example, the following rules highlight the cell of the player, and color water differently depending on whether it is covered by fog. The condition not at(R,C,player) makes sure that a cell with the player on it (for instance, on a bridge over water) gets only one background color.
decorate(bgcolor,R,C,hex_ffd) :- at(R,C,player).
decorate(bgcolor,R,C,hex_aac) :- at(R,C,water), observe(fog(R,C)),
not at(R,C,player).
decorate(bgcolor,R,C,hex_ddf) :- at(R,C,water), not observe(fog(R,C)),
not at(R,C,player).
Items that are not observable to the player can also be shown on the grid, for example to see what the player is missing, or to help debug a game. For this, the visibility program can use atoms of the form decorate(item,R,C,O), which lead to item O being shown in the cell R,C. Such items use the same icons and colors as other items, but are drawn faded, to distinguish them from what the player observes.
Atoms over decorate derived by the visibility program are only used for the visualization: they are *not* passed on to the player's program. So they cannot reveal anything to the player.
For example, the following rule in the visibility program shows every item that the player cannot observe.
decorate(item,R,C,O) :- at(R,C,O), not observe(at(R,C,O)).
Auxiliary
This is an auxiliary program that is added to all programs.
Level generation
This program expresses how levels are generated.
Visibility
This program expresses what the player can observe.
Nature's moves
This program expresses nature's moves—i.e., how the state of the board changes as a result of the player's moves.
Winning conditions
This program expresses when the game is won or lost.
Player strategy
This program computes the player's move(s).
Level settings
Level state
Game output
Below you will find, for each time step (most recent step at the top), what facts were fed into the player program, based on (i) the state of the game that is observable to the player, and (ii) the player's memory. It also shows what came out of the step: the player's moves, the updates to the player's memory, and the next state of the game computed by nature. If a program had no answer sets or contained an error, this is reported as well.
This tab explains how the different logic programs of a game talk to each other: what each program gets as input, which parts of its answer set are kept, and where those parts go next. It describes what the game engine (js/game.js) actually does. The Instructions tab gives the same picture in less detail.
The basic mechanism
The programs never call each other directly. All communication goes through plain text, handled by the game engine. Every time a program is used, the engine does the following:
- It glues together a number of pieces of text: some rules added by the engine itself, facts computed earlier (such as the current state of the game), and one or more of the programs from the Game rules and Strategy tabs. The pieces are concatenated in a fixed order, separated by newlines.
- It runs clingo on the result, with the options -n1 -Wnone --heuristic=Domain. Only the first answer set that clingo finds is used. (So a game is only random through RANDINT or #heuristic statements, not because clingo picks an answer set at random.)
- It filters the answer set: only atoms with certain predicate names are kept. For example, from the player's answer set only atoms over do, remember and forget are kept. Everything else (helper predicates and so on) is thrown away.
- It writes the kept atoms back as facts (a. b. c.), which become input text for the next program.
Between programs, the engine keeps the following pieces of text:
- Level state: the at(R,C,O) facts of the initial state of the level (shown under Level state in the level editor).
- Level settings: the setting(...) and decorate(...) facts of the level, which stay the same during the whole game.
- Game state: the whole history of the game, as facts at_time(T,R,C,O), meaning that object O is in cell (R,C) at time T, and facts state_time(T), one for every time T so far. A new time slice is added at each step; old slices are never removed. The state_time facts make it possible for a time slice to be empty (when there are no objects left).
- Player input: the observe(...) and setting(...) facts that the player gets to see at the current step, and the fact current_time(T).
- Decorations: the decorate(...) facts that are used to draw the grid at the current step.
- Player memory: the memory(...) facts the player carries from one step to the next. It starts out empty.
Most programs do not work with at_time directly. Instead, the engine adds the following two rules, which pick out the latest time and present it as current_time(T), and the objects at that time as plain at(R,C,O) facts:
current_time(T) :- T = #max { S : state_time(S) }.
at(R,C,O) :- at_time(T,R,C,O), current_time(T).
Below, these two rules are called the current-state rules.
Before the game: generating the level
When Generate level is pressed, the engine takes the level generation program, replaces every RANDINT(l,u) by a random number, and adds the auxiliary program. From the first answer set:
- atoms over at (and debug) become the level state;
- atoms over setting and decorate become the level settings.
If there is no answer set, both are left empty and there is no level to play.
When the game starts, the level state is turned into the initial game state by this small program:
at_time(0,R,C,O) :- at(R,C,O). state_time(0). % ... followed by the level state
Only the at_time and state_time atoms are kept. So everything in the state of the game is a triple (R,C,O) at some time T; other atoms in the level state (such as debug) are dropped here.
One time step, in order
Right before the first step, the engine runs the visibility program once on the initial state, to get the first player input. Then every time step consists of the following, in this order:
- Winning conditions. The goal program is run on the current state. If it derives lose or win, the game ends here. This happens before the player moves, so in step 1 the initial state is checked as well.
- Engine timeout. If 500 steps have already been played, the engine stops the game with TIMEOUT!, which counts as a loss. This catches games that would otherwise go on forever.
- Drawing the grid. The grid is drawn from the player input, the decorations and the level settings. So the grid shows what the player sees, plus the decorations, not the true state of the game.
- Player strategy. The player program is run on the player input and the player's memory. This gives the moves (do) and the memory updates (remember, forget).
- Memory update. The memory updates are applied to the player's memory.
- Nature. The nature program is run on the current state and the player's moves. Its next atoms become the next time slice of the game state.
- Memory wipe. If nature derived wipe_player_memory, the player's memory is emptied. This happens after step 4, so whatever the player remembered in this step is lost too.
- Visibility. The visibility program is run on the new state, giving the player input and decorations for the next step.
The Game output in the Debug tab follows this order. For each step it shows the player input, the player's memory, the player's moves, the memory updates and the next state from nature. Whenever a program has no answer set or contains an error, the engine uses one of the fallbacks described below, and the game output says so (for example - Nature program: no answer set (state unchanged)).
Overview per program
The table below lists, for every program run by the engine, what text is glued together (in this order), which atoms are kept from the answer set, and where they go.
| Program | Input (in this order) | Atoms kept | Where they go | RANDINT? | If there is no answer set |
|---|---|---|---|---|---|
| Level generation | level generation program, auxiliary program | at, debug setting, decorate |
level state level settings |
yes | no level |
| Initial state (built in) | at_time(0,R,C,O) :- at(R,C,O). state_time(0)., level state | at_time, state_time | game state | – | empty game state |
| Visibility | current-state rules, game state, visibility program, level settings, auxiliary program | observe, setting, current_time decorate |
player input (and the grid) the grid only |
no | empty player input and no decorations |
| Player strategy | player input, player memory, auxiliary program, player strategy program | do remember, forget |
nature program memory update |
yes, anew every step | no moves, memory unchanged |
| Memory update (built in) | two rules (see below), player memory, memory updates | memory | player memory | – | memory unchanged |
| Nature | current-state rules, game state, player moves, nature program, level settings, auxiliary program | next wipe_player_memory |
next time slice of the game state memory wipe |
yes, anew every step | everything stays where it is (and no wipe) |
| Winning conditions | current-state rules, game state, level settings, auxiliary program, winning conditions program | lose, win | ends the game (lose is checked first) | no | the game continues |
| Drawing the grid (not a logic program) | player input, decorations, level settings | setting(grid_height(..)), setting(grid_width(..)), observe(at(..)), observe(fog(..)), decorate(..) | the picture | – | – |
Visibility
The visibility program sees the true state of the game, as at(R,C,O) (and, if it wants to, the full at_time history), plus the level settings. It decides what the player is allowed to know by deriving atoms over observe. The player input consists of these observe atoms together with all setting atoms (the latter come straight from the level settings, which are part of this program), and current_time(T) (from the current-state rules). So the player never gets raw at(R,C,O) facts: only observe(...), setting(...) and current_time(...).
The decorate atoms of this answer set are used only for drawing the grid. They include the decorate atoms from the level settings, because those are part of the program too. None of them are passed to the player.
Player strategy
The player program gets exactly four things: the player input (observe, setting and current_time facts), the player's memory (memory facts), the auxiliary program, and the player's own program (with RANDINT replaced anew in every step). It does not see the game state, the decorations, or the other programs. From its answer set, do atoms are passed to nature as facts, and remember/forget atoms are passed to the memory update.
Memory update
If the player derived at least one remember or forget atom, the engine runs the following program, followed by the current memory and the memory updates:
new_memory(X) :- memory(X), not forget(X). new_memory(X) :- remember(X).
The new_memory atoms are then renamed to memory, and these form the player's new memory. So remember wins over forget: an atom that is both remembered and forgotten in the same step is kept. A memory(...) atom that the player program derives itself (rather than gets as input) is not stored: only remember stores things.
Nature
Nature sees the true state, the player's moves as do(...) facts, and the level settings. From its answer set the engine keeps the next(R,C,O) atoms (and current_time(T)), and turns them into the next time slice with this program:
at_time(T+1,R,C,O) :- next(R,C,O), current_time(T). state_time(T+1) :- current_time(T). % ... followed by the game state and the next atoms
Note that only next atoms survive: an object that has no next atom simply disappears from the game. This is why nature programs usually contain rules like next(R,C,flag) :- at(R,C,flag). If there are no next atoms at all, the new time slice is empty, but time still advances (thanks to the state_time rule).
If the nature program has no answer set (for instance because a constraint like :- do(X), do(Y), X != Y. is violated), the engine falls back to next(R,C,O) :- at(R,C,O).: time moves on but nothing changes. In that case the memory is not wiped.
Winning conditions
The goal program sees the current state, the level settings, and current_time(T), which is the number of steps taken so far (0 at the start). If its answer set contains any atom over lose, the game is lost. Otherwise, if it contains any atom over win, the game is won. Otherwise the game continues.
Worked example 1: Find the flag 1
Let's follow the first step of Find the flag 1 (with solution) on an 8x6 grid. The player starts in the top-left cell and the flag is at row 5, column 7. (The facts are shown one per line here. The engine writes them on one line, and their order may differ, but order does not matter.)
Level generation. The answer set of the level generation program gives the following level state and level settings:
% Level state (atoms over at) at(1,1,player). at(5,7,flag). % Level settings (atoms over setting and decorate) setting(grid_width(8)). setting(grid_height(6)). setting(time_bound(15)). setting(row(1)). setting(row(2)). ... setting(row(6)). setting(col(1)). setting(col(2)). ... setting(col(8)). decorate(game_type,grid). decorate(label,flag,font_f024). decorate(label,player,font_f007).
Initial state. The level state becomes time slice 0:
at_time(0,1,1,player). at_time(0,5,7,flag). state_time(0).
Visibility. The visibility program (observe(at(R,C,O)) :- at(R,C,O).) is run on the current-state rules, the game state above, the level settings and the auxiliary program. The current-state rules derive current_time(0), at(1,1,player) and at(5,7,flag), and from there the visibility program derives the observe atoms. The player input is:
observe(at(1,1,player)). observe(at(5,7,flag)). current_time(0). setting(grid_width(8)). setting(grid_height(6)). setting(time_bound(15)). setting(row(1)). ... setting(row(6)). setting(col(1)). ... setting(col(8)).
and the decorations are the three decorate facts of the level settings.
Step 1, winning conditions. The goal program sees current_time(0), at(1,1,player) and at(5,7,flag). Neither win nor lose(timeout) is derived, so the game continues.
Step 1, player strategy. The player program is run on the player input above, the (empty) memory, the auxiliary program and the player strategy:
do(move(down)) :- observe(at(R1,C1,player)), observe(at(R2,C2,flag)), R1 < R2. do(move(right)) :- observe(at(R,C1,player)), observe(at(R,C2,flag)), C1 < C2.
Since the player is in row 1 and the flag in row 5, the answer set contains do(move(down)). This is the only atom that is kept. There are no remember or forget atoms, so the memory stays empty. The player's moves are:
do(move(down)).
Step 1, nature. The nature program is run on the current-state rules, the game state, the fact do(move(down))., the level settings and the auxiliary program. The kept atoms are:
current_time(0). next(2,1,player). next(5,7,flag).
These are turned into time slice 1 and added to the game state, which now reads:
at_time(0,1,1,player). at_time(0,5,7,flag). at_time(1,2,1,player). at_time(1,5,7,flag). state_time(0). state_time(1).
Step 1, visibility. The current-state rules now derive current_time(1), at(2,1,player) and at(5,7,flag); the old slice 0 is still in the game state but no longer counts as at. The player input for step 2 is:
observe(at(2,1,player)). observe(at(5,7,flag)). current_time(1). setting(grid_width(8)). % ... and the other setting facts, as before
The rest of the game. The same cycle repeats. The player moves down until it reaches row 5 (after step 4), and then moves right. After step 10 the game state has slices 0 to 10, with at_time(10,5,7,player) and at_time(10,5,7,flag) in the last one. At the start of step 11, the goal program sees current_time(10), at(5,7,player) and at(5,7,flag), so it derives win, and the game is won. (The time bound of 15 is not reached, so lose(timeout) is not derived.)
Worked example 2: fog and memory
The first example has no hidden information and no memory. This second example is a small made-up game on a 1x4 grid: the player starts in cell (1,1), there is an item amnesia in cell (1,3), and the flag is in cell (1,4). The auxiliary, nature and winning conditions programs are those of Find the flag 1, with two extra rules in the nature program:
next(R,C,amnesia) :- at(R,C,amnesia). % Stepping onto amnesia wipes the player's memory wipe_player_memory :- next(R,C,player), at(R,C,amnesia).
The visibility program only lets the player see its own cell and the cells next to it:
observe(fog(R,C)) :- cell(R,C), at(R2,C2,player), |C-C2| >= 2. observe(at(R,C,O)) :- at(R,C,O), not observe(fog(R,C)).
The player always moves right, counts its steps in memory, and remembers where the flag is once it has seen it:
remember(steps(1)) :- not memory(steps(_)). forget(steps(N)) :- memory(steps(N)). remember(steps(N+1)) :- memory(steps(N)). remember(flag_at(R,C)) :- observe(at(R,C,flag)). do(move(right)).
Step 1. The player is in (1,1), so cells (1,3) and (1,4) are covered by fog. The amnesia item and the flag are not observed. The player input is:
observe(at(1,1,player)). observe(fog(1,3)). observe(fog(1,4)). current_time(0). % ... plus the setting facts
The memory is empty, so the player derives remember(steps(1)) (and do(move(right))). The memory update turns this into the new memory memory(steps(1)). Nature moves the player to (1,2).
Step 2. From (1,2), the amnesia item in (1,3) can be seen, but the flag in (1,4) is still covered by fog. The player gets:
observe(at(1,2,player)). observe(at(1,3,amnesia)). observe(fog(1,4)). current_time(1). % ... plus the setting facts memory(steps(1)).
The memory updates are forget(steps(1)) and remember(steps(2)), which gives the new memory memory(steps(2)). But nature moves the player onto the amnesia item, so its answer set contains wipe_player_memory, and the memory is emptied. The steps(2) that was just remembered is lost too.
Step 3. The player is in (1,3) and can now see the flag. Its memory is empty, so it starts counting again from 1:
% Player input observe(at(1,3,player)). observe(at(1,3,amnesia)). observe(at(1,4,flag)). observe(fog(1,1)). current_time(2). % ... plus the setting facts % Memory updates derived by the player remember(steps(1)). remember(flag_at(1,4)). % New memory memory(steps(1)). memory(flag_at(1,4)).
Nature moves the player to (1,4). At the start of step 4, the goal program derives win.
Appendix: notes
Some behaviour of the engine that is easy to miss. Most of these points are also mentioned in the Instructions tab.
- The player gets observe, not at. The player gets the observe(...) atoms from the visibility program, the setting(...) atoms, current_time(...), its memory(...), and the auxiliary program, but never the state of the game itself.
- lose takes precedence over win. The engine checks lose first, so if both are derived, the game is lost.
- The full history is available. The game state contains all at_time facts since time 0, and this is passed to the visibility, nature and goal programs. These programs could use at_time directly to look at earlier states. The history also grows with every step, so each step's programs get a little larger.
- Only at/3 is game state. Anything nature derives other than next (and wipe_player_memory) is lost. Any information that should last has to be encoded as an object in a cell.
- RANDINT is only replaced in some programs: level generation, player strategy and nature. It is not replaced in the visibility, winning conditions or auxiliary programs.
- Nature without next atoms empties the board. If the nature program has an answer set but derives no next atoms, the fallback is not used, and the new time slice is empty. Time still advances, because the game state also records state_time(T) for every time T. (The fallback that keeps everything in place is only used when the nature program has no answer set.)
- Level generation leftovers. debug atoms from level generation end up in the level state, but are dropped when the initial state is made. The decorate atoms from level generation are part of the level settings, so the visibility, nature and goal programs can see them (the player cannot).
- Memory details. If an atom is both remembered and forgotten in the same step, it is kept. If the player derives no remember or forget atoms at all, the memory update is skipped and the memory stays as it is.
- Win/lose timing. Win and lose are checked at the start of each step, before the player moves, so an initial state that already satisfies the winning conditions is won in step 1.
- Stratification check. There is code to check that the player program is stratified and "simple" (using stored/stratified.lp), but the call to it is commented out, so player programs are currently not restricted.