BF Joust strategies

From Esolang
Jump to navigation Jump to search

BF Joust is more involved than its simple rules might suggest. This page details some of the major strategies which are used, and gives a short example for each.

Attack

Rush

Rush-based programs focus entirely on attack, though they may use any attack strategy. They tend to be further divided into "slow rushes" that set large decoys, and "fast rushes" which try instead to reach the opponent before they can set up their own decoys.

The rule of nine

There is no purpose in attacking the first 9 cells, since the enemy's flag will never occupy one of them. For this reason, it is common to start fast rush programs with code similar to

(>)*9

Slow rush programs will instead generally set up decoys and then move to the tenth cell of the tape and start attacking from there, in the hope of avoiding decoys that were set in the range of cells that cannot possibly contain the flag.

Many programs want to skip the first nine cells this way, and then do something (such as a clear) on each of the potential remaining 21 cells. In such cases, the simplest code puts 8 of the > commands into an initial repeat, and then the ninth inside a loop, in order to give nine > instructions but not run off the end of a length-30 tape if the "something" fails:

(>)*8(>do something)*21

The only reason a rush program might want to not start at the tenth cell is if it's specifically trying to detect decoys, as in tripwire avoidance; even though the enemy flag cannot be in the first 9 cells, enemy decoys can be, and this can sometimes produce useful information.

Exploiting the fact that an opponent uses this tactic is difficult or impossible; in theory it can give away information about the tape length, but it's too easily confused by decoys and similar tactics.

Clear

The most obvious strategy is to look for any set cells and zero them as in Brainfuck.

[-]

The canonical example of this strategy is

(>)*9([-]>)*21

Trace and animation

This is known as a "two-cycle clear", and is fast and common, but can often be exploited by defence programs. A slower "three-cycle clear" looks like this:

[-.]

(or alternatively with the - and . reversed). Three-cycle clears are less generally exploitable by defence programs; sometimes programs use five-cycle clears to make absolutely sure. (Four-cycle clears are not useful as they are generally locked by the same code that locks two-cycle clears.)

The vast majority of programs use some kind of clear. A simple non-offset clear such as this one, though, is very vulnerable to decoys; a decoy with one polarity (positive or negative depending on whether the clear is based on [-] or [+]) will have almost no effect, but a decoy with the other polarity will cause the clear to take much longer, as it has to cycle through all the possible values until it reaches 0 from the other direction. Another method of exploiting an enemy clear is to lock it; this requires knowing or guessing the cycle length of the clear, and thus two-cycle clears, being the most common, are also the most vulnerable to this.

Offset clear

It is common to set values in several cells as decoys. The values of these decoys are usually very close to zero, often 255 (-1) or 1. As such, using a clear such as

[-]

will result in the maximum cycles spent looping to clear a cell. This can be offset by first adding an expected decoy value.

(+)*9[-]

The canonical example of an offset clear program (with offset 9) is

(>)*9([(+)*9[-]]>)*21

Trace and animation

Offset clears are less prone to decoys than regular clears, but can still be defeated by decoys larger than the offset. They are slower on average than typical clears, too, and almost as vulnerable to locking (unless the offset is large enough to throw off the timing of the lock). Additionally, offset clears have a distinctive pattern of zeroing on a small tripwire of the right polarity (first zeroing for one cycle, and then later zeroing for two cycles), meaning that a carefully placed tripwire can detect the presence, polarity, and offset of an offset clear, making locking it very easy unless it varies from one tape position to the next.

Wiggle clear

A further extension of the offset clear concept is to have nested loops in opposite directions, to catch small values quickly. For instance, the clearing algorithm

([-{ ([+{ [-] }])%8 }])%4

will finish quickly with values from -4 to 4. The inner clear algorithm can be anything, including a larger offset clear. Combining wiggle clear with offset clear yields the canonical program

(>)*9 ( ([-{ ([+{ (+)*9[-] }])%8 }])%4 >)*21

Trace and animation

One downside of the canonical wiggle clear is the part where it closes a lot of brackets in a row before moving on to the next cell. This renders it vulnerable to triplocks, which begin rebuilding a just-cleared decoy by the time the third closing bracket is reached, preventing the algorithm from ever being able to leave the triplocked cell. The most straightforward way to avoid this is to add an extra clear before every closing bracket in this row. A canonical offset wiggle clear algorithm with triplock avoidance might look like:

(+)*4 ([-{ [(-)*15 [+]] }[-]])%8

Note that this algorithm will be stuck clearing the triplocked cell of a good triplocker for nine consecutive triplock phases, and this may be enough for the triplocker to win on short tapes. However, it typically takes more than 9 triplock phases to full clear a long tape, at which point this clear algorithm will finish and the program using it will move on the next cell. If the triplocker uses the tripwire method to wait for its opponent to clear its triplock, it will sit waiting forever while the program using this clear moves on to take out its flag.

Here is a direct incorporation of the above clear algorithm into an otherwise simple program defeating a sophisticated triplocker on all tape lengths and polarities: Trace and animation

Careless clear

The careless clear, also known as the turtle strategy, is a fast way of clearing cells which are equal to the value 128, the initial flag value. Rather than looping, the attacker simply adds or subtracts 128 from the cell, then waits one cycle in case the flag is zero. This saves one cycle per iteration (since there are no true iterations), but is expensive on small values.

(-)*128.

This of course will only have the desired result if the flag is not changed by the opponent. The canonical example of this careless clear is

(>)*9([(-)*128.>]>)*21

Trace and animation

The careless clear can also be combined with the concept of the offset clear to support some range of values by waiting after a sequence of modifications rather than only the last one. For instance, to support values from 112 to 144:

(-)*112(-.)*32

Trace and animation

Although very fast against large decoys, careless clears can typically be tricked off the end of the tape simply by changing the value of your own flag (although the amount often has to be quite large); for much the same reason, they tend to be defeated by shudder-based programs, and often also by vibration-based programs. Setting a lot of decoys helps as well, as small and moderate-sized decoys can be set much faster than the careless clear can clear them.

Reverse offset clear

When you suspect you're on an opposing flag, but fear the opponent might have used anti-careless-clear tactics, it is possible to use an offset clear centered about 128, rather than 0; instead of a small offset then clearing in the opposite direction to the offset, it uses an offset just below 128 followed by a clear in the same direction. The following program does this, after first checking for decoys of size 1:

>+>-(>)*6(>[-[++[(+)*120[+]]]])*21

Trace and animation

The advantage of this over a careless clear that checks a range of values is that it's guaranteed to clear an undefended cell eventually even if it's been set to a very unusual value for a flag, rather than running off the tape like a careless clear would; and the advantage over a regular offset clear is that for flags near 128, it's almost twice as fast. The disadvantage is that if the cell isn't actually a flag, it can take much longer than a typical offset clear would, leaving it vulnerable to being outraced. It's thus mostly useful only if you have a good idea that the current cell is a flag.

Anti-shudder clear

Due to the danger of stepping beyond the opponent's flag and losing when the opponent changes their own flag to zero and back again, it is now necessary to make sure to close at least two brackets before moving to the next cell. Generally speaking, this consists of a simple clear like a two-cycle clear, followed by a clear with a much large cycle length in order to beat defenders and shudderers. This following example does this, with a two-cycle offset clear followed by an eleven-cycle clear, and in addition ensures that the canonical shudder program will die in as few cycles as possible:

(>)*8(>[(+)*9[-].[.++-------]])*21

Trace and animation

Such clears are only slightly slower than typical clears (and likewise, an offset anti-shudder clear is comparable and slightly slower than a straight offset clear), and are hard to take advantage of via defensive programs. Lock-based programs can sometimes lock such clears on their first, simple clear, though, although generally only if they know their polarity (the lock will break eventually otherwise, but can sometimes take a very long time to do so, sometimes long enough for the lock-based program to clear three or four tape cells even via the slow full-tape clear method).

Timer clear

One interesting way to evade locks is to simply stop clearing after a while and move onto a different strategy; this allows fast two-cycle clears to be used to defeat rush programs, whilst still not being trivially locked by defence-based programs. Here's a simple example, showing a program that changes clear direction and period after 500 clear attempts, and running it against a trivial lock-based program that tries to lock the sort of clear that the program starts with into an infinite loop and a draw (in practice, lock-based programs would probably be trying to win, but the same principle applies):

(>)*8(>([-{[+.]}>])%500)*21

Trace and animation

The major issue with timer clears is their inflexibility; although it is relatively easy to make an offset and/or anti-shudder timer clear, most other modifications are difficult or impossible. (For instance, they cannot check to see if a cell is 0 before offsetting it, like most offset clear algorithms do.)

Flexible timer clear

The inflexibility of timer clears can be effectively worked around simply by embedding multiple timer clears inside each other, putting each inside timer clear just after the > of the one immediately afterwards. This allows each of the individual clears to be on a separate timer; it also allows pretty much arbitrary modifications to the clear algorithm, which can even be different for differently positioned decoys. Here's an example using offset clears with multiple different offsets:

(>)*8(>(+)*24([-{[+.]}>(+)*23
             ([-{[+.]}>(+)*22
             ([-{[+.]}>(+)*21
             ([-{[+.]}>(+)*20

(etc...)

             )%500])%500])%500])%500)*-1

It works as a drop-in replacement for standard sorts of clear, as it works identically before the timer expires; the main disadvantage is its length, which can make it hard to fit into more control-heavy programs. Unlike regular timer clears, it cannot reasonably be nested, and so can be locked if the defending program finds a lock pattern that works on both the inside and the outside clear loops.

Full-tape clear

If the opponent does not do anything, it is possible to definitively clear the opposing flag in constant time by setting every element on the tape to each of its 256 possible values and waiting a turn. Naive code to do that looks something like this:

(>)*8(>(+.)*255)*21

Trace and animation

Such an approach is both incredibly slow and prone to locking, and thus rarely if ever seen in successful rush programs. However, its fixed-cycle and easily interruptible nature makes it incredibly valuable in programs that use locks themselves; if the opponent is successfully locked, a defensive program can alternate between maintaining the lock and continuing its full-tape clear. As such, this is considered a very defensive form of attack, generally used only by defense programs to finish off their opponent.

To beat a full-tape clear, all that is necessary is to be able to act freely, rather than stuck in a lock; almost any aggressive technique will outrace it, and many defensive techniques will beat it too, although often with a tape length dependency.

Poke

A slow rusher can usually win by having more decoys than its opponent, and there are two ways to do that: by making your opponent have fewer decoys, and by building yourself more decoys. Poking is a strategy that makes both of these things possible. The idea is to find out where your opponent is before building any of your own decoys, and then adjusting your decoy line forward so as to make room for more of your own decoys, and also possibly to identify a safe location behind your opponent's decoys, depending on how they build them. Here is an example:

(>)*8(>[(<)*8(-)*33(>)*8([-[++[(+)*9[-]]]]>)*21])*21

Trace and animation

Pokes can be made less effective by racing to a point far from the flag early on in order to set a decoy there, in order to cause them to think the tape is shorter than it is; this is rarely fatal to the opposing program, but will often slow it down. Additionally, rushing forwards quickly gives a chance to get behind the opponent's decoys before they are set up, making the enemy flag very easy to find. Thus, poke-based programs normally need a separate countermeasure against fast rush programs.

Poke can be combined with the wiggle clear to produce a deep poke. This strategy was first used in Gregor_furry_furry_strapon_pegging_girls, see its behavior against Deewiant_pendolino for a good example: Trace and animation

Tripwire avoidance

Any program that assumes the first nonzero cell it sees is probably a decoy and keeps searching. Here is an example:

(>)*8(>[(>[(+)*9[-]]>)*21])*21

Trace and animation

Note that it will suicide against any opponent that doesn't make decoys. Or, that doesn't make them fast enough for it to see them. (Tripwire avoiders will sometimes wait deliberately, and change the Rule of Nine to a Rule of Seven or whatever, in order to make sure that if the opponent makes decoys, it spots them. However, this removes some of their speed advantage.)

Tripwire avoidance can be defeated by setting up no decoys, but this is often a bad idea due to the huge disadvantage it gives against other sorts of programs. Alternatively, setting up an extra small decoy is often all that is needed to neutralize the tactic.

An effective way to avoid tripwires but not necessarily lose to fast rushers, is to check for decoys once per two cells. This will only lose to no decoys half the time, while still being fairly good at tripwire avoidance. Here's a simple program that does this:

>>+>>>>>(>>[(-)*7[+]++>])*-1

The obvious countermeasure against such programs is to only set decoys an even distance from your flag.

Reverse tripwire avoidance

For small reverse tripwires, it is possible to avoid triggering them by restoring them to their original value before moving away from them, such as the following example that restores all size 1 decoys: (Warning: untested)

(>)*8((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]_>((>[-[{(++[(+)*9[-]]_>
((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->
((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->
((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->
((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->((>[-[{(++[(+)*9[-]]->
((>[-[{(++[(+)*9[-]]->[-]{}])}]]+)%1)*2{}])}]]+)%2)*2{}])}]]+)%3)*3{}])}]]+)%4)*4
{}])}]]+)%5)*5{}])}]]+)%6)*6{}])}]]+)%7)*7{}])}]]+)%8)*8{}])}]]+)%9)*9{}])}]]+)%10)*10{}])}]]+)%11)*11
{}])}]]+)%12)*12{}])}]]+)%13)*13{}])}]]+)%14)*14{}])}]]+)%15)*15{}])}]]+)%16)*16{}])}]]+)%17)*17
{}])}]]+)%18)*18{}])}]]+)%19)*19{}])}]]+)%20)*20{}])}]]+)%21)*21

As you can see, it is not very compact or flexible. Futhermore, this only works if the program using them does not check them constantly, as in this case, it would check them before you had a chance to restore them. However, the primary benefit of reverse tripwires is their ability to allow a program to go do other things before determining if the tripwire has been tripped, so most programs that use them will not check them immediately. However, in the age of larger reverse tripwires, it may become prohibitively expensive to restore their value.

In the case of programs that use small reverse tripwires and check them incessantly, just checking several times in a row if a cell is nonzero before attempting to modify it can avoid them in many timings/tape lengths (at the cost of slowing down your decoy clear by a few cycles):

(>)*8(>[[[[+[--[(+)*9[-]]++]-]]]])*21

Reverse decoy avoidance

If you assume that the opponent uses a reverse decoy setup, you can look for a nonzero cell, assume it's the first of a series of reverse decoys, then keep moving until you find an empty cell, then move again until you find a nonempty cell, asssume it's a flag, and clear it (e.g. with a reverse offset clear, wiggled to avoid small decoys, as shown in the example below):

>>>>(>[[>]((>++++[-[-[-[-[-[-[-[-[(-)*120[-]{}]]]]]]]]])%29)*-1])*-1

As written, the strategy is trivially defeated by a trail or forward decoy setup, making it a very fast risky rushing strategy. (Some protection against trails can be added either by looking for small values on cells, which has problems of its own, or more plausibly deciding that a reverse decoy setup is probably not in use after seeing a suspiciously large number of decoys.) It also tends to be quite sensitive to the details of the opposing programs (the details of decoy setups tend to vary wildly).

Interrupted rush

On short tape lengths, rushers benefit from rushing as quickly as possible to prevent the enemy taking a lot of time to build more and larger decoys, or at least to tear through their decoys nearly as fast as they can be built. However, since decoy building is always faster than decoy clearing, on longer tapes it benefits one to slow down the opponent by having a lot of decoys. Thus, a very effective strategy is to rush for a while, then go back and build more decoys when you believe your initial set of decoys has almost been destroyed, then go back and rush some more. Examine Deewiant_pendolino in the Major Programs section for an example of this strategy. One might also wish to interrupt a rush to do other things, like switch to a defense strategy, or repair your own flag a little bit. These sorts of ideas are advanced strategies that have not yet been thoroughly explored.

Defense

Primarily defensive programs that aim to win via flag fall generally have to be quite complex in order to be able to attack while defending, and as such the examples used here are generally ineffectual on their own and sometimes not very short. They are presented simply to give the gist of a particular strategy, without being a good program in and of themselves. Defensive tactics can sometimes also be useful even for rush programs, in order to buy more time for their rush.

Decoy

The most fundamental defensive strategy is the decoy. Since it is impossible for an attacker to distinguish the flag from any other non-zero cell, filling a section of the tape with various non-zero values can slow down or even foil an attack. It is important to use both positive and negative decoys, so that your decoys slow down your opponent equally in either polarity.

(>+>-)*4

Decoys can be further split into "small decoys" that are less than the range of a typical offset clear (or in the case of a wiggle clear, the offset of the wiggle) and serve only to trick the opposing program into thinking that something is there (in order to defeat tripwire-avoiders, confuse pokes, etc.); and "large decoys" that are designed to be large enough to force even offset clears with the wrong polarity to clear the cell in the wrong direction. It is also common to refer to "medium decoys", which are larger than the wiggle offset of a typical wiggle clear, but smaller than the much larger offset that follows the wiggle.

Since decoys are unlikely to cause an opponent to lose on their own, they are almost always combined with other strategies. They are common in all sorts of programs to buy time to perform their strategies (in rush programs, to try to buy time with an undefended flag in order to zero the enemy flag first; in defence programs, in order to set up tripwires and adjust cells on which locks will be performed to sane starting values).

A few programs may benefit from not setting up decoys. This normally happens with bad programs, where setting up decoys may not aid their strategy because it was a bad idea in the first place, whereas not setting up decoys can trick tripwire avoiders off the end of the tape; this is not particularly useful, though, as it won't correct the fundamental flaws in the original program. Alternatively, not using decoys can benefit defence programs that don't use tripwires either, in order to make the likely timings of the opponent a little more consistent, and aid them in setting up a successful lock or shudder.

Small decoys are typically avoided via offset clears. For large decoys, using a fast clear is one possible method to avoid them. However, techniques like tripwire avoidance and careless clears work better by entirely neutralising the decoy's effect, and defensive programs, especially those that use tripwires for synchronization, generally care nothing for opposing decoys, either due to not attacking at all, or due to using full-tape clears that are unaffected by decoy values.

Reverse decoy setup

When setting a large number of decoys, it is often a good idea to set the ones nearer the enemy flag first, in order that the first few decoys might buy time to set up the last few against faster rush programs.

>>>>>(-)*64<(+)*64<(-)*64<(+)*64(>)*7(>[-])*21

Here's a comparison (decoy setups versus a simple rush program):

The major disadvantage of a reverse decoy setup is on shorter tapes, where instead of some of a large number of decoys working, none do (assuming that the opponent skips them via the Rule of Nine), and the program is defeated trivially. Therefore, it is often a good idea to gain an idea of what the tape length is before embarking upon such a system. Unless the first few decoys are small and set up quickly, reverse decoy setups also often lose to tripwire avoidance, as the first decoys are skipped before the later decoys are set up. If it's suspected that the opponent is using a reverse decoy setup, it's also possible to skip cells until a zero cell is found, but doing so is incredibly risky as it will suicide against other sorts of programs.

Interrupted decoy setup

Just as building the decoys furthest from your flag can give you more time to build more decoys behind it, one can also use the time afforded by the building of a decoy to do other things. This could be considered an "advanced" strategy. Check out the animation/trace of quintopia_space_elevator in the Major Programs section to see a decoy build interrupted to check a reverse tripwire. Also, see Deewiant_pendolino for an example of a decoy build interrupted to rush the opponent.

Breadcrumb decoys

By making a series of very small decoys as a trail, then reversing to the beginning of the tape and building them further, it is possible to build decoys precisely until the opponent is encountered. This is done by using the small decoys as reverse tripwires to see if the opponent has come this far already. The idea is to check a reverse tripwire immediately before each decoy is built and interrupt the build as soon as the opponent has been detected, changing strategies based on when and where the opponent was last seen. This is a special type of interrupted decoy build.

Here is a small example program that uses this strategy:

>-(>[<<<(+)*10(<+[{}(+)*23>(--[(>)*7(>[(-)*15[+]])*21](-)*23>)*24])*24]+)*10
<<<(+)*10(<+[{}(+)*23>(--[(>)*8(>[(-)*15[+]])*21](-)*23>)*24])*24

Trace and animation

Also, compare the early-game behavior (immediately after the deep poke) of Gregor_furry_furry_strapon_pegging_girls, the program that introduced it, with quintopia_a, a later champion that uses it, as they compete: Trace and animation

Trail

Any attacker which zeroes cells may also choose to change that value before moving on to the next cell. That changed value can effectively be a decoy. For instance, the below program combines a simple clear with a trail.

(>+>-)*4>([-]+>[-]->)*11

Trace and animation

Leaving a trail generally helps against slowly rushing enemies, as it effectively gives you a lot of free decoys. It hurts against fast rush programs, though, which will generally have overtaken you before the trail is even set, and in such cases a trail will just slow you down. (Not to mention, that offset clears can easily avoid a typical trail.) A typical defence program won't even notice whether you're trailing or not.

Lock

Upon somehow deducing the location of the enemy data pointer (e.g. via a tripwire), and its clear mechanism (e.g. guessing a two-cycle clear or two-cycle offset clear), it is possible to turn the cell in question in the other direction at much the same rate, thus forcing the opponent into an infinite loop. Locking can also be done less reliably by turning the cell in the same direction, such that the cell never becomes zero for two cycles; the risk in this is that the opponent can detect that it's happening and take countermeasures. If a defense program is lucky, the opponent will respond by falling off the tape, but rush programs rarely fall for such tactics nowadays. Here is possibly the simplest lock-based program:

>+[]<(.)*90
((+)*256 (>)*9 (+.)*119 (<)*9)*3
((+)*256 (>)*10 (+.)*118 (<)*10)*3
((+)*256 (>)*11 (+.)*117 (<)*11)*3
((+)*256 (>)*12 (+.)*116 (<)*12)*3
((+)*256 (>)*13 (+.)*115 (<)*13)*3
((+)*256 (>)*14 (+.)*114 (<)*14)*3
((+)*256 (>)*15 (+.)*113 (<)*15)*3
((+)*256 (>)*16 (+.)*112 (<)*16)*3
((+)*256 (>)*17 (+.)*111 (<)*17)*3
((+)*256 (>)*18 (+.)*110 (<)*18)*3
((+)*256 (>)*19 (+.)*109 (<)*19)*3
((+)*256 (>)*20 (+.)*108 (<)*20)*3
((+)*256 (>)*21 (+.)*107 (<)*21)*3
((+)*256 (>)*22 (+.)*106 (<)*22)*3
((+)*256 (>)*23 (+.)*105 (<)*23)*3
((+)*256 (>)*24 (+.)*104 (<)*24)*3
((+)*256 (>)*25 (+.)*103 (<)*25)*3
((+)*256 (>)*26 (+.)*102 (<)*26)*3
((+)*256 (>)*27 (+.)*101 (<)*27)*3
((+)*256 (>)*28 (+.)*100 (<)*28)*3
((+)*256 (>)*29 (+.)*99 (<)*29)*3

Trace and animation

Locks can be broken by using a different clear algorithm from the one they expect; using a three-cycle clear is typically enough for this, and a five-cycle clear nearly always escapes locks perfectly. Varying clear algorithms from tape cell to tape cell makes it much harder for a lock to guess what you are doing. Locks are also incapable of trapping careless clears and full-tape clears on any particular cell, but often beat them anyway by incidentally tricking them off the end of the tape, so they cannot be seen as sensible countermeasures.

Another way to break free from a lock is to change strategy after a while, either based on time, or on observing a zero cell for one cycle but not two (a giveaway that you are either being locked, or facing a vibration or shudder program). The anti-shudder clear exploits this by dropping through into something crazy, like an eleven-cycle clear, that no program could be expected to have an appropriate lock algorithm for, or to figure out that that clear algorithm was being used even if it did.

Probabilistic lock

The invention of the timer clear meant that new defensive strategies were needed for defence programs to be able to maintain their locks. The probabilistic lock abandons any attempt to synchronize with an opponent; rather, it resigns itself to the fact that it will only get a lock some proportion of the time.

The basic idea is to pick a length of time that's a small power of 2 (such as 128 cycles), then throughout most of that time, increase then decrease the cell you're locking on. Here's an example from ais523_preparation (which increases and decreases the cell by 56.5 cycles, alternating the rounding between iterations):

(+)*56(-)*57(.)*15
(+)*57(-)*56(.)*15

Because the lock's repeat rate is a power of two and makes no net change to the lock cell, the sequence of values on the lock cell will have the same repeat rate as the opponent's clear loop would naturally; in particular, this means that the lock loop and the opposing clear loop will always be at the same point whenever the lock cell hits zero. Almost half the time, the lock is working with the opponent; almost half the time, it's working against. However, the value of the lock cell moves faster when the two loops are moving it in the same direction, than when they're moving it in opposite directions. Thus, the odds are in favour of both loops pushing in the same direction as the lock cell crosses zero, which is the most important condition for a lock to hold.

This sort of lock thus gives a chance to defeat timer clears. Because it does not care that much about the details of the enemy's clear loop, there is a good chance that, having changed clear loop upon the timer expiry, the new loop will immediately be locked again by the probabilistic lock.

The huge downside is that this sort of lock only leaves a very small window to do anything other than maintain the lock. The above example gives fifteen cycles of freedom each time round the lock; this is enough to (very slowly) check or adjust cells within seven cells of the lock, but does not allow any sort of full-tape clear. Typical uses of this time would be to set up very strong decoys, and/or to check tripwires to see if the lock had broken (which can happen by chance, due to the lack of any meaningful sort of synchronization).

Triplock

A triplock is a different sort of lock, which targets a different part of the opponent's program. While ordinary locks are almost exclusively aimed at the opponent's clear loop, a triplock instead tries to lock the opponent in its control flow; to be precise, it locks programs that contain three or more ] commands in a row, which is very common in constructs such as [+[--[-]]], and can appear in programs for other reasons. (To be precise, it locks programs that contain three ] commands with no < or > in between immediately after a clear (with the first ] possibly part of the clear loop), and also certain other control flow patterns.)

The basic idea is to wait until a decoy (that cannot be the flag, unlike an ordinary lock) becomes zeroed, then immediately set it up to a large nonzero value again, using tripwire code such as [](+)*128. In order to actually win with the lock, it needs to be combined with a clear. A full-tape clear can be used, as typically is with locks; but a triplock (and no other known defensive construct, at the moment) also allows a much shorter "inline clear", allowing for a complete program that looks much like this:

>(+)*128[](+)*128>(+)*15[-]<[](+)*128>>(+)*15[-]<<[[](+)*128>>[>]->([-{[<+]}])%10<[<]<]

Trace and animation (warning: can be very slow to load on several browsers)

(The inline clear works by using the tape itself as working space to remember how far through its clear algorithm it is; but because it takes a variable rather than fixed amount of time like a full-tape clear does, it cannot be combined with an ordinary lock.) Other clears can also be used, such as 2-cycle clears or offset clears, but run quite a risk of the opponent breaking the lock if clearing one cell takes too long.

The simplest way to beat a triplock is merely to avoid the sort of control flow structures it is vulnerable to, but this can often cripple a program for other reasons (inability to use complex control flow is quite serious). Alternatively, certain types of clear algorithms can avoid triplocks by breaking the lock faster than it can complete its clear (reverse offset clears tend to do this, often on only one polarity depending on the details of the triplock). It may also be possible for a program to detect that it has been triplocked, although ordinary locks pushing in the wrong direction look much the same from the point of view of a program, so programs need to experiment further to determine exactly how the opponent is trying to lock them.

There is an interesting symmetry between triplock and shudder programs, incidentally; shudder programs tend to beat programs that check the value of the tape cell they are on once before moving on, whereas triplock programs tend to beat programs that check the value three or more times before moving on. Two would thus seem to be the optimal number, in a program that can afford the control flow restrictions that come with it.

Flag repair

Flag repair is a defensive technique used in situations where the opponent is known to have reached your flag before you have started to clear theirs (thus meaning that you would likely lose if you attempted to outrace them), but have a good idea of where their flag is already (e.g. the tape is short, or the opponent appears to be a fast rush program). The basic idea is to make a large adjustment (between 64 and 128) to your own flag at top speed (e.g. (+)*120 on your flag) – if the opponent's clear loop goes in the other direction to the adjustment you have delayed them by twice that many cycles (thus catching up in the flag-clearing race by that many cycles), and if it is in the same direction you will probably push the flag past zero and force them to clear it again (unless they manage to observe it at zero, and use the information to reverse the direction of the clear loop). Repairing the flag buys a little extra time which is often sufficient to defeat fast rush programs or to win on short tapes; it also defeats careless clears (something that is also possible by adjusting your flag while the opponent isn't clearing it, but doing it while the opponent is clearing is better against non-careless-clear strategies).

Repeatedly repairing the flag is, in effect, a lock algorithm, and as such, it is possible for a flag repair to serve as the first part of a lock.

Shudder

Shuddering is cycling the value of the flag through increments and decrements in order to cause attack loops to fail to put the flag at 0 for two cycles. The reversal of direction is key to this strategy. It usually results in a net increase/decrease over time, differentiating it from a "vibrator" below. Although moderately good as a defensive strategy, it tends to lose at random on certain tape lengths, and it renders its user entirely incapable of attacking as the shudder needs to be kept up constantly. Shudder programs thus can only win by tricking the opponent off the end of the tape. Canonical example:

(++-)*100000

Trace and animation

As shuddering can only win by tricking its opponent off the end of the tape, waiting for two zero cycles in a row before moving on guarantees at least a draw against it, and often a win. Clears designed to exploit this property are known as anti-shudder clears.

Vibrator

A vibrator vibrates its own flag between zero and nonzero constantly, thus making it very dangerous to assume a flag is cleared just because it hit zero. However, it is very easy to defeat by using a clear that is out of phase with its vibration. Canonical example:

(-)*127(-+)*100000

Trace and animation

If you fear falling off the tape due to missing a vibrator, you can check two cycles in a row before moving on (the difference between this and counter-shudder tactics is that it must be done at the start of the loop, rather than the end).

Abandoning a defensive strategy

Defensive programs sometimes have to face other defensive programs. Some defensive programs cannot gain information about what the opponent is doing while they are actively defending, e.g. programs that shudder or vibrate the flag usually need to do so continuously to avoid losing. However, many defensive techniques offer spare cycles; many of these may be used to clear decoys (or to bolster your own decoys) and eventually the opponent's flag, but some of them can typically be spared to check reverse tripwires, or even the zeroness/nonzeroness of the cell that the program is trying to lock the opponent on. For these programs, it is usually possible for them to detect situations in which the opponent is being purely passive and has not even reached the lock cell, either via checking whether tripwires (regular or reverse) have been tripped, or by checking to see whether the lock cell is 0 on cycles where it would be 0 if the opponent did nothing.

If the opponent does appear to be passive, it is generally a good idea to switch to a rush strategy tuned specifically to defeat defensive programs, rather than continuing to defend. If done correctly, this gives you wins rather than draws against programs that defend forever, and gives you a chance of a win rather than a loss against a program that switches to anti-defense rush after noticing that you are passively defending.

Reconnaissance

Although a program that entirely ignores its opponents attacks (and in some cases, even the opponent's defences) can be effective, many programs will want to discover information about their opponent's strategy, the opponent's current actions (e.g. to synchronize a lock), the tape length, or some combination of these.

Tripwire

A tripwire is one of the simplest reconnaissance and synchronization devices, detecting an enemy clear loop.

An ordinary tripwire looks like this:

+[]

What it does is obvious, as it causes you to wait for your opponent to clear it. Using one is guaranteed to make you draw with any non-attacking program.

A variation is the two-cycle tripwire:

+[[]]

which waits for a cell to be zeroed for two cycles before you can continue. (The similarity of this to BF Joust's main victory condition means that all but careless clears are likely to do this, and probably just before moving to the next cell, rather than as part of an offset clear).

Sometimes tripwires will have a value larger than 1 or -1; this is typically for the purpose of causing the length of time it takes to zero them to provide more information about the opponent's strategy.

Tripwire avoidance allows obvious tripwires to be skipped altogether, giving an easy win, but is itself fooled by decoys. Careless clears will not trip an ordinary tripwire at one polarity, nor a two-cycle tripwire at either polarity, meaning that they can often beat tripwire-based programs too. (However, tripwire-based programs that change the value of their flag can exploit this effect in reverse, to beat careless clears by tricking them off the end of the tape!)

Timer tripwire

A normal tripwire represents a significant commitment; the program will not leave its tripwire until it is tripped, and if the tripwire is never tripped, the warrior cannot win. But a quality defense program is afraid of commitment, and wants to be able to change its mind if it thinks the tripwire will never get tripped. If it also cannot tolerate the synchronization inaccuracies associated with a reverse tripwire, it can use a timer tripwire, wherein it stops monitoring the tripwire after a preset number of cycles:

++++++([{give up and switch strategies}][handle careless or offset clear]synchronization achieved)%1000

Reverse tripwire

An alternative sort of tripwire, that allows other operations to be done at the same time.

+go do some other processing-[tripped]not tripped

This allows you to go do other things like building decoys or deciding whether you want to attack instead of wait any longer. As long as you check it often enough, you can get almost as close to being in sync with your opponent as you could be using a regular tripwire, and yet still be able to change your mind about defending, perhaps due to a belief that the opponent is also a defender.

The reverse tripwire has three major weaknesses: an opponent can set it back to its previous value (generally 1 or -1) upon clearing it; if the opponent encounters it while its value happens to be 0, they may miss its existence entirely and skip past it; and a cautious opponent can observe that its value becomes 0 every now and then (a giveaway for a reverse tripwire or a vibrated flag) and choose not to clear it. (Also, they aren't cycle-accurate with respect to timing like regular tripwires are; for some defensive programs, this matters, for most it won't.) Sometimes values greater than 1 or -1 are used for reverse tripwires in order to reduce the risks, but that also means they take longer to check. Tripwire avoidance also works almost as well against reverse tripwires as it does against regular tripwires. Using multiple reverse tripwires is common in programs that use any, as a method of increasing the chance that at least one spots the opponent, and helping to defeat countermeasures.

Decoy detection

Most programs will not change a tape element that's already set to zero, even if they encounter it during a clear loop; and even if they do (e.g. as part of a trail), if a program changes a tape element, that's a sure sign that it at least knows that the tape is long enough to contain that element. As such, decoys beyond that point are not helpful.

You can detect enemy decoys simply by doing a [ on an element before setting a decoy there. If you discover it to be nonzero, there is no point in placing a decoy there or anywhere beyond that point. Lymia_nyuroki was the first program to exploit this in a reverse decoy setup, to detect clashing decoy patterns. An earlier and more common use involves placing decoys up to the first enemy decoy detected (e.g. via using a large trail); this is often wasteful against aggressive programs who set few decoys (as they will rule-of-nine past some), but helps against programs that set a lot of decoys (because the rule of nine is less useful for those programs), and against defensive programs (where the time wasted setting up unused decoys is not very costly, and thus setting up possibly-useful-possibly-useless decoys is worthwhile as the upside outweighs the downside).

Tape length estimation

The most important value in all of BF Joust is the tape length, and thus it's very helpful for programs to get some idea of what it is.

One sure sign of a short tape is if you see an opponent's decoy early, before there would be enough time for a program to move there from the other side of a long tape and set a decoy there. In order to defend against pokes, it is common for programs to start by running forwards, leaving a small trail as they go – if both programs do that, and the tape is short, it is quite likely that they will encounter each others' decoys early on.

Not 100%, but still fairly reliable, is noticing that the opponent has somehow skipped past some of your decoys and tripwires (e.g. via observing a reverse tripwire having been tripped even though there was no time to clear a decoy in front of it); although there are various possible explanations, the most likely is that they were skipped using the rule of nine. Depending on where the opponent was first observed, you can estimate that the tape length is probably somewhere around 9 cells away from there. This test works in reverse, too – if an opponent seems to be spending time clearing a particular decoy, the end of the tape is probably at least nine cells away from there (although you may need to verify that the opponent is indeed clearing it, rather than setting a decoy there!)

Generally speaking, it is important nowadays for programs to attempt to determine whether the tape is particularly short – tactics that work well on long tapes, such as setting large numbers of decoys, tend to do badly on short tapes. Some strategies that work well on short tapes are fast rushes, careless clears, and lock-based defence (because you may win before the opponent can break the lock, and because it is good against careless clears). It is also possible for programs to optimise for longer tapes if they see no evidence of a short tape (e.g. by upgrading the rule of nine to a rule of ten or rule of eleven).

See also