I like rollerskating, but sometimes it’s kind of boring to skate by the same routes. I was using Pokemon GO for making the route more fun, but pokestops have fixed locations and catching the pokemons after a few months is kind of boring too. So I thought that it can be interesting to randomly select places to skate. And snake game makes it even more interesting and challenging because I need to select a more complex route for avoiding snake’s tail and not losing the game.
Although sometimes the app puts candies on the other side of the road or requires me to ride on a sidewalk with intolerable quality, I solved it with an option to regenerate the candy.
TLDR: the app, source code.
What’s inside
The app is written in JavaScript with flow, React Native and redux with redux-thunk. For the map, I used react-native-maps which are nice, because it works almost out of the box. So mostly the game is very simple.
The first challenging part is candies generation. As a first attempt the app uses nearby search from Google Places API (hah, it’s already deprecated) with a specified radius, filters places with the radius greater than minimal and selects random place. As we can’t just use coordinates for filtering by distance, I used node-geopoint library.
const generateCandyFromPlacesNearby = async (
position: Position,
): Promise<?Position> => {
const positionPoint = new GeoPoint(position.latitude, position.longitude);
const response = await fetch(
"https://maps.googleapis.com/maps/api/place/nearbysearch/json?" +
`location=${position.latitude},${position.longitude}` +
`radius=${config.CANDY_MAX_DISTANCE}`,
);
const { results } = await response.json();
const availablePositions = results.filter(({ geometry }) => {
const point = new GeoPoint(geometry.location.lat, geometry.location.lng);
return positionPoint.distanceTo(point, true) > constants.CANDY_MIN_DISTANCE;
});
return sample(availablePositions);
};
If there’s no place with appropriate distance in the specified radius, the app just chooses a random latitude and longitude offset within specified bounds.
const generateCandyFromRandom = (position: Position): Position => {
const point = new GeoPoint(position.latitude, position.longitude);
const [minNE, minSW] = point.boundingCoordinates(
constants.CANDY_MIN_DISTANCE,
undefined,
true,
);
const [maxNE, maxSW] = point.boundingCoordinates(
constants.CANDY_MAX_DISTANCE,
undefined,
true,
);
switch (random(3)) {
case 0:
return {
latitude: random(minNE.latitude(), maxNE.latitude()),
longitude: random(minNE.longitude(), maxNE.longitude()),
};
case 1:
return {
latitude: random(minSW.latitude(), maxSW.latitude()),
longitude: random(minNE.longitude(), maxNE.longitude()),
};
case 2:
return {
latitude: random(minNE.latitude(), maxNE.latitude()),
longitude: random(minSW.longitude(), maxSW.longitude()),
};
default:
return {
latitude: random(minSW.latitude(), maxSW.latitude()),
longitude: random(minSW.longitude(), maxSW.longitude()),
};
}
};
And the last complicated part is detecting if the player touches snake’s tail. As we store tail as a list of coordinates, the game just checks if the head within aspecified radius of the tail parts.
export const isTouched = (
a: Position,
b: Position,
radius: number,
): boolean => {
const aPoint = new GeoPoint(a.latitude, a.longitude);
const bPoint = new GeoPoint(b.latitude, b.longitude);
return aPoint.distanceTo(bPoint, true) <= radius;
};
export const isSnakeTouchedHimself = (positions: Position[]): boolean =>
some(positions.slice(2), position =>
isTouched(positions[0], position, constants.SNAKE_TOUCH_RADIUS),
);
* like Pokemon GO without a camera.