Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pig capturing scenario #1258

Merged
merged 3 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/scenarios/Challenges/Ranching/00-ORDER.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
capture.yaml
gated-paddock.yaml
33 changes: 33 additions & 0 deletions data/scenarios/Challenges/Ranching/_capture/design-commentary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Synchronized capture

Two approaches to implement the goal-checking of this scenario were considered.
In both, the goal is reached when the "pig" is surrounded on all four sides by barriers.

The difference is is when the barriers are not synchronized:
1. In the first approach, a goal condition is defined that detects if any barrier is adjacent to the pig without being surrounded on all sides. The primary goal condition treats this as a negated prerequisite, so that the scenario is instantly lost.
2. In the second (adopted) approach, this secondary goal was preserved, but only as an optional achievement. Instead, the pig as a system robot implements the detection of an un-synchronized approach. The pig retreats a short distance, providing another opportunity for the player to attempt a synchronized approach (though players may simply choose to restart the scenario).

In implementing this second approach, it is essential that the pig react to barrier placement within a single tick. We do not want to allow the player to place one barrier later than the others and simply be too slow to detect this un-synchronized state, counting it as a win. This single-tick reaction is accomplished with the `instant` command.

## Retreat behavior
If a barrier comes in contact with the pig without surrounding on all sides, the pig "runs away" in a straight line opposite from the "base" robot, irrespective of any entities or impassible terrain, until no barriers are adjacent. This may entail traversing multiple cells, which happens instantaneously, due to the single-tick reaction requirement described above.

This escape strategy is simplified by finite availability of `unwalkable`, `portable` entities in this scenario.

## Solution

There at least two ways to solve this challenge, both of which require building child robots.
1. Use the `watch` command to synchronize two pairs of robots. Use symmetry and placed them equidistantly with respect to the watched location and their monolith.
2. Launch each child robot from the same location. Write custom code for each built robot to account for the delay incurred by building each. Adjust the timing by trial and error.

## Alternative game mechanic applications

Other physical phenomena could rationalize this "squeezing" game mechanic.

* The synchronized "squeeze" from all sides is evocative of the ["implosion method" of a fission weapon](https://en.wikipedia.org/wiki/Nuclear_weapon#/media/File:Fission_bomb_assembly_methods.svg). Synchronization is required to avoid a [fizzle](https://en.wikipedia.org/wiki/Fizzle_(nuclear_explosion)). A [PNE](https://en.wikipedia.org/wiki/Peaceful_nuclear_explosion) has applications in:
* [nuclear synthesis](https://en.wikipedia.org/wiki/Synthesis_of_precious_metals)
* [space travel](https://en.wikipedia.org/wiki/Nuclear_pulse_propulsion)
* [asteroid redirection](https://en.wikipedia.org/wiki/Asteroid_impact_avoidance)
* Instead of an explosion, the barriers could serve as [neutron reflectors](https://en.wikipedia.org/wiki/Neutron_reflector) for a fission reaction. See [demon core](https://en.wikipedia.org/wiki/Demon_core) for consequences of uncontrolled (un-synchronized) reflectors
* A high-pressure squeeze could create [artificial diamonds](https://en.wikipedia.org/wiki/Synthetic_diamond) (HPHT method).

114 changes: 114 additions & 0 deletions data/scenarios/Challenges/Ranching/_capture/opponent.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
If any blocking entity is touching me, escape.
*/

def elif = \t. \then. \else. {if t then else} end
def else = \t. t end
def abs = \n. if (n < 0) {-n} {n} end;
def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;
def until = \p. \c. q <- p; if q {} {c; until p c} end;

def getDirection = \n.
if (n == 0) {
forward;
} $ elif (n == 1) {
right;
} $ elif (n == 2) {
back;
} $ elif (n == 3) {
left;
} $ else {
down;
};
end;

def watchDir = \n.
watch $ getDirection n;
if (n > 0) {
watchDir $ n - 1;
} {};
end;

def boolToInt = \b.
if b {1} {0}
end;

/**
Iterate 4 times (once for each direction)
*/
def countBlocked = \n.
if (n > 0) {
isBlocked <- blocked;
// This assignment has to happen before the recursive
// "countBlocked" call due to #1032
let localBlockCount = boolToInt isBlocked in
turn left;
lastCount <- countBlocked $ n - 1;
return $ lastCount + localBlockCount;
} {
return 0;
}
end;

def reWatch =
watchDir 4;
end;

def locationIsOpen =
emptyHere <- isempty;
blockedCount <- countBlocked 4;
return $ emptyHere && blockedCount == 0;
end;

def faceAwayFrom = \loc.
myLoc <- whereami;
let x = fst loc - fst myLoc in
let y = snd loc - snd myLoc in
let d = if (abs x > abs y) {
if (x > 0) {west} {east}
} {
if (y > 0) {south} {north}
} in
turn d;
end;

/**
Move in opposite direction from base to
find a cross-shaped region that has zero blockages.
*/
def findOpenArea =
baseLoc <- as base {whereami};
faceAwayFrom baseLoc;
until locationIsOpen move;
end;

def relocateAway =
marker <- grab;
findOpenArea;
try {
swap marker;
return ();
} {
place marker;
};
end;

def handleBlockage =
blockedCount <- countBlocked 4;
if (blockedCount > 0) {
if (blockedCount < 4) {
relocateAway;
} {
selfdestruct;
}
} {};
end;

def go =
instant reWatch;
wait 1000;
instant handleBlockage;
go;
end;

go;
123 changes: 123 additions & 0 deletions data/scenarios/Challenges/Ranching/_capture/solution.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
Need to synchronize pushes so that in the same tick
that the first boulder comes into contact with the
opponent, there is no escape route.
*/

def doN = \n. \f. if (n > 0) {f; doN (n - 1) f} {}; end;

def intersperse = \n. \f2. \f1. if (n > 0) {
f1;
if (n > 1) {
f2;
} {};
intersperse (n - 1) f2 f1;
} {};
end;

def walkAround = \d1. \d2.
turn d1;
doN 3 move;
turn d2;

doN 3 move;
turn d2;
end;

def buildBot = \initialTurn.
_r1 <- build {
watch back;
wait 200;
doN 2 move;
turn initialTurn;
walkAround right left;
push;
};

_r2 <- build {
watch back;
wait 200;
doN 2 move;
turn initialTurn;
walkAround left right;
push;
};
end;

def arrangeBoulders =
// First boulder
turn right;
move;
turn left;
doN 7 move;
turn left;
push;
turn right;
move;
turn left;
move;
turn left;
doN 4 push;

// Second boulder
turn back;
doN 7 move;
turn left;
doN 2 move;
turn left;
doN 5 push;

// Third boulder
turn right;
doN 4 move;
turn left;
doN 7 move;
turn left;
move;
turn left;
doN 3 push;
turn left;
move;
turn right;
move;
turn right;
push;

// Fourth boulder
turn right;
doN 3 move;
turn left;
doN 2 push;
turn right;
move;
turn left;
move;
turn left;
push;
end;

def go =

doN 3 move;
obj <- grab;

arrangeBoulders;

turn right;
doN 2 move;
turn back;
buildBot right;
turn back;
move;

turn left;
move;
buildBot left;
turn back;
move;

place obj;
move;
end;

go;
Loading