Skip to content

Commit

Permalink
snake game (#1699)
Browse files Browse the repository at this point in the history
Demos use of a string to maintain a queue of coordinates.

![Screenshot from 2024-01-02 10-34-05](https://github.com/swarm-game/swarm/assets/261693/eaa24d48-40dc-4294-8243-2977caf4b79f)
  • Loading branch information
kostmo authored Jan 3, 2024
1 parent 1da74a7 commit ea28633
Show file tree
Hide file tree
Showing 5 changed files with 323 additions and 1 deletion.
3 changes: 2 additions & 1 deletion data/scenarios/Fun/00-ORDER.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
GoL.yaml
logo-burst.yaml
horton.yaml
horton.yaml
snake.yaml
160 changes: 160 additions & 0 deletions data/scenarios/Fun/_snake/snake.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/**
Uses a string to maintain a queue of coordinates.
*/

def coordsToString : (int * int) -> text = \coords.
format (fst coords) ++ "," ++ format (snd coords)
end

def indexOfRec : int -> text -> text -> (unit + int) = \pos. \inputString. \targetChar.
if (pos >= chars inputString) {
inL ()
} {
if (toChar (charAt pos inputString) == targetChar) {
inR pos
} {
indexOfRec (pos + 1) inputString targetChar
}
}
end

def indexOf : text -> text -> (unit + int) =
indexOfRec 0
end

// Drops the first character of a string
def strTail : text -> text = \inputString.
snd $ split 1 inputString
end

def splitOnFirstChar : text -> text -> (text * text) = \inputString. \splitChar.
case (indexOf inputString splitChar) (\_.
// Did not find the split character, so return the original string
(inputString, "")
) (\foundIdx.
let parts = split foundIdx inputString in
(fst parts, strTail $ snd parts)
)
end

def getDecimalCharValue = \inputString. \idx.
charAt idx inputString - charAt 0 "0"
end

// Works from right to left
def parseDecimalRec : int -> text -> int = \charsRemaining. \inputString.
if (charsRemaining > 0) {
getDecimalCharValue inputString (charsRemaining - 1) + 10 * parseDecimalRec (charsRemaining - 1) inputString
} {0}
end

def parseDecimal : text -> int = \inputString.
let isNegative = toChar (charAt 0 inputString) == "-" in
let negationMultiplier = if isNegative {-1} {1} in
let modifiedString = if isNegative {strTail inputString} {inputString} in
let stringLength = chars modifiedString in
negationMultiplier * parseDecimalRec stringLength modifiedString;
end

// Comma (",") is the separator between abscissa and ordinate
def stringToCoords : text -> (int * int) = \coordsString.
let pair = splitOnFirstChar coordsString "," in
(parseDecimal $ fst pair, parseDecimal $ snd pair)
end

// APPEND to string representation of a coordinate list
def snoc : (int * int) -> text -> text = \coords. \strList.
let delimiter = if (chars strList > 0) {";"} {""} in
strList ++ delimiter ++ coordsToString coords;
end

// Extracts the first element and returns the shortened list
def pop : text -> (unit + ((int * int) * text)) = \strList.
if (chars strList > 0) {
let pair = splitOnFirstChar strList ";" in
inR (stringToCoords $ fst pair, snd pair)
} {
inL ();
}
end

def getDir = \dest.
path (inL ()) (inL dest);
end;

def doAtLoc = \currLoc. \targetLoc. \func.
teleport self targetLoc;
x <- func;
teleport self currLoc;
return x;
end;

def moveTail = \tailList.
emptyHere <- isempty;
if emptyHere {
let maybeShifted = pop tailList in
case maybeShifted (\_.
// Nothing to pick up or replace
return tailList;

) (\newPair.

let farthestTail = fst newPair in
let newInit = snd newPair in
newLoc <- whereami;
grabbedItem <- doAtLoc newLoc farthestTail grab;
place grabbedItem;

return $ snoc newLoc newInit;
);
} {
return tailList;
}
end;

def moveOneStep = \tailList.

// This robot will always be sitting atop the apple
r <- robotnamed "spawn";
targetLoc <- as r {whereami};

maybeD <- getDir targetLoc;
case maybeD (\_. say "Dead!"; return "") (\d.
turn $ fst d;
newList <- moveTail tailList;
move;
return newList
);
end

// Invariant: No tail pieces shall ever be moved underneath the
// snake robot, unless an apple was just picked up.
def moveToApple = \tailList.

myLoc <- whereami;

appleHere <- ishere "apple";
if appleHere {

modifiedTailList <- try {
make "tail";
swap "tail";
return $ snoc myLoc tailList;
} {
grab;
return tailList;
};
// Need to move here so that we get out of the way
// if the tail gets elongated
moveOneStep modifiedTailList;
} {
moveOneStep tailList;
}
end;

def go = \tailList.
newList <- instant $ moveToApple tailList;
go newList;
end;

go "";
35 changes: 35 additions & 0 deletions data/scenarios/Fun/_snake/spawn.sw
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
def waitForConsumption =
watch down;
wait 2000;
appleHere <- ishere "apple";
if appleHere {
waitForConsumption;
} {}
end;

// TODO Ensure we don't spawn inside a coil of the tail
def placeAtOpenLocation = \range.
randX <- random range;
randY <- random range;
let x = range/2 - randX in
let y = range/2 - randY in
teleport self (x, y);
emptyHere <- isempty;
if emptyHere {
place "apple";
} {
placeAtOpenLocation range;
}
end;

def repeatedlyPlaceApple = \range.
placeAtOpenLocation range;
waitForConsumption;
end;

def go =
instant $ repeatedlyPlaceApple 20;
go;
end;

go;
122 changes: 122 additions & 0 deletions data/scenarios/Fun/snake.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
version: 1
name: Snake
author: Karl Ostmo
seed: 1
description: |
Watch the snake eat the apples
creative: false
objectives:
- goal:
- |
Eat and "digest" many apples
condition: |
r <- robotnamed "snake";
as r {
appleCoreCount <- count "apple core";
return $ appleCoreCount >= 40;
}
robots:
- name: base
dir: north
devices:
- ADT calculator
- branch predictor
- clock
- comparator
- compass
- dictionary
- grabber
- hourglass
- hearing aid
- keyboard
- lambda
- logger
- net
- scanner
- strange loop
- string
- treads
- wayfinder
- name: snake
dir: north
system: true
display:
attr: green
invisible: false
devices:
- rattle
program: |
run "scenarios/Fun/_snake/snake.sw"
- name: spawn
dir: north
system: true
display:
invisible: true
inventory:
- [100, apple]
program: |
run "scenarios/Fun/_snake/spawn.sw"
solution: |
noop
entities:
- name: wayfinder
display:
char: 'w'
description:
- |
Enables the `path` command
properties: [known, portable]
capabilities: [path]
- name: apple
display:
char: 'a'
attr: red
description:
- |
Tasty snack
properties: [known, portable]
- name: apple core
display:
char: 'I'
attr: wood
description:
- |
Remains of an eaten apple
properties: [known, portable]
- name: rattle
display:
char: 'r'
attr: green
description:
- |
Device unique to a snake
properties: [known]
- name: tail
display:
char: '@'
attr: green
description:
- |
Segment of snake's tail
properties: [known, unwalkable]
recipes:
- in:
- [3, apple]
out:
- [1, tail]
- [3, apple core]
required:
- [1, rattle]
known: []
world:
dsl: |
{grass}
upperleft: [0, 0]
palette:
'B': [grass, erase, base]
'S': [grass, erase, snake]
's': [grass, erase, spawn]
'.': [grass, erase]
map: |
B..
s.S
4 changes: 4 additions & 0 deletions test/integration/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ testScenarioSolutions rs ui =
, testTutorialSolution Default "Tutorials/world101"
, testTutorialSolution (Sec 5) "Tutorials/farming"
]
, testGroup
"Fun"
[ testSolution (Sec 10) "Fun/snake"
]
, testGroup
"Challenges"
[ testSolution Default "Challenges/chess_horse"
Expand Down

0 comments on commit ea28633

Please sign in to comment.