Первым встал вопрос, откуда брать данные, а именно фотографии и координаты точек. Пару лет назад нашу страну покинул такой проект, как Ingress, представлявший собой гео игру в дополненной реальности. В свою очередь, я посчитал, что раз проект решил отказаться от нас, как игроков, я посчитал морально оправданным ~~спиз~~экспропреировать кусочек их данных, а именно спарсил с их карты intel.ingress.com т.н. «порталы», которые, по сути и есть эти самые геоточки с фотографиями.
Дамп я загнал в Postgresql с подключенным расширением Postgis[1].
1: https://postgis.net/
Ну а далее написал достаточно простой API на Golang, который реализует следующие методы:
Создание новой игровой сессии, в ответ ставится кука внутри которой зашифровано текущее состояние — ник, количество очков, ID текущего угадываемого объекта (в начале пустое).
POST /api/state Content-Type: application/json { "username": "NeonXP" }
Получение состояния. Просто возвращает вышеуказанные параметры
GET /api/state
Выдача нового объекта для угадывания. При этом возвращается ссылка на фото и обновляется состояние, тем что в него вписывается ID объекта
POST /api/next
Угадывание. Собственно, на вход передаются координаты куда на карте указал игрок. А в ответ возвращается:
* Название объекта
* Расстояние от переданной точки до реального размещения объекта
* Geojson строка в которой зашифрована линия соединяющая точку и объект (нужна для отрисовки красной линии на карте)
При этом высчитываются очки которые получает игрок за попытку по формуле max(1000-d, 0), где d - расстояние между выбранной точкой и объектом в метрах. То есть, если разница меньше 1000м, то чем ближе - тем больше очков (максимум 1000 очков за 1 очень точное угадывание).
POST /api/guess Content-Type: application/json { "lat": 55.123, "lon": 49.123 }
Вот в общем-то и всё API!
Из интересностей, при выборе очередной точки у неё в БД увеличивается счетчик, а сам select выбирает случайную точку только среди тех точек, где этот счетчик минимальный. То есть, пока не будут выданы игрокам все точки, уже выбранные заново не будут выданы. Вот это место в коде: https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n26[2] (стр. 26-32)
2: https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n26
err = btx.NewSelect(). ColumnExpr(`p.guid, p.img`). Model(r). Where(`p.count = (SELECT MIN(pl.count) FROM places pl WHERE pl.deleted_at IS NULL)`). OrderExpr(`RANDOM()`). Limit(1). Scan(ctx, r)
Ещё я бы отметил то, что я решил по максимуму логику вынести в БД, и, например, при угадывании расстояние до точки, а также вышеупомянутый geojson формируются так же на стороне БД: https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n50[3] (стр. 50-59)
3: https://git.neonxp.ru/guessr.git/tree/pkg/service/places.go#n50
err := p.db.NewSelect(). Model(&model.Place{GUID: guid}). WherePK("guid"). ColumnExpr(`p.name, p.guid, p.img, ST_Distance(ST_MakePoint(?, ?)::geography, p.position::geography)::int AS distance, ST_AsGeoJSON(ST_MakeLine( ST_SetSRID(ST_MakePoint(?, ?), 4326), ST_SetSRID(p.position, 4326) )) AS geojson`, lon, lat, lon, lat). Scan(ctx, r)
В комментах к анонсу ребята накидали достаточно много хороших идей, синтезировав которые, и добавив свои хотелки я составил примерно такой чеклист:
Как-то так :) А впереди новые выходные и новые «проекты выходного дня»!