aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--content/posts/2024-12-12-guessr/index.md106
-rw-r--r--content/posts/2024-12-12-guessr/logo.jpgbin0 -> 121344 bytes
-rw-r--r--content/projects/guessr.md8
3 files changed, 114 insertions, 0 deletions
diff --git a/content/posts/2024-12-12-guessr/index.md b/content/posts/2024-12-12-guessr/index.md
new file mode 100644
index 0000000..81125f7
--- /dev/null
+++ b/content/posts/2024-12-12-guessr/index.md
@@ -0,0 +1,106 @@
++++
+title = "Guessr"
+description = ""
+date = 2024-12-12T22:27:49+03:00
+categories = [ "Мои проекты" ]
+tags = [ "IT", "Проект выходного дня" ]
+location = "Казань"
+image="logo.jpg"
++++
+
+На недавних выходных я запилил очередной «проект выходного дня». На этот раз — аналог известного сервиса GeoGuessr, но
+в отличие от него, все точки сконцентрированы в моей родной Казани. Ну и я не использую панорамы, а фотографии мест.
+
+Я обещал выложить исходники, и в общем, вот они: https://git.neonxp.ru/guessr.git/
+
+## Немного про разработку
+
+Первым встал вопрос, откуда брать данные, а именно фотографии и координаты точек. Пару лет назад нашу страну покинул
+такой проект, как Ingress, представлявший собой гео игру в дополненной реальности. В свою очередь, я посчитал, что раз
+проект решил отказаться от нас, как игроков, я посчитал морально оправданным ~~спиз~~экспропреировать кусочек их данных,
+а именно спарсил с их карты intel.ingress.com т.н. «порталы», которые, по сути и есть эти самые геоточки с фотографиями.
+
+Дамп я загнал в Postgresql с подключенным расширением [Postgis](https://postgis.net/).
+
+Ну а далее написал достаточно простой API на Golang, который реализует следующие методы:
+
+- Создание новой игровой сессии, в ответ ставится кука внутри которой зашифровано текущее состояние — ник, количество
+ очков, ID текущего угадываемого объекта (в начале пустое).
+ ```http
+ POST /api/state
+ Content-Type: application/json
+
+ {
+ "username": "NeonXP"
+ }
+ ```
+- Получение состояния. Просто возвращает вышеуказанные параметры
+ ```http
+ GET /api/state
+ ```
+- Выдача нового объекта для угадывания. При этом возвращается ссылка на фото и обновляется состояние, тем что в него
+ вписывается ID объекта
+ ```http
+ POST /api/next
+ ```
+- Угадывание. Собственно, на вход передаются координаты куда на карте указал игрок. А в ответ возвращается:
+ - Название объекта
+ - Расстояние от переданной точки до реального размещения объекта
+ - Geojson строка в которой зашифрована линия соединяющая точку и объект (нужна для отрисовки красной линии на карте)
+ При этом высчитываются очки которые получает игрок за попытку по формуле max(1000-d, 0), где d - расстояние между
+ выбранной точкой и объектом в метрах. То есть, если разница меньше 1000м, то чем ближе - тем больше очков (максимум
+ 1000 очков за 1 очень точное угадывание).
+ ```http
+ 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
+(стр. 26-32)
+```go
+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 (стр. 50-59)
+
+```go
+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)
+```
+
+# Дальнейшие планы
+
+В комментах к анонсу ребята накидали достаточно много хороших идей, синтезировав которые, и добавив свои хотелки я
+составил примерно такой чеклист:
+
+- [ ] Авторизация и общая доска лидерства
+- [ ] После угадывания спрашивать у игрока «сложность», чтобы потом можно было, например, настраивать чтобы попадались
+ только простые объекты. И, например, разное количество очков за простые и сложные объекты
+- [ ] Подумать как вынести игру в оффлайн, по типу того же ингресса. Это сложно и предстоит хорошо это обдумать
+
+Как-то так :) А впереди новые выходные и новые «проекты выходного дня»! \ No newline at end of file
diff --git a/content/posts/2024-12-12-guessr/logo.jpg b/content/posts/2024-12-12-guessr/logo.jpg
new file mode 100644
index 0000000..d9cfbfd
--- /dev/null
+++ b/content/posts/2024-12-12-guessr/logo.jpg
Binary files differ
diff --git a/content/projects/guessr.md b/content/projects/guessr.md
new file mode 100644
index 0000000..11c679d
--- /dev/null
+++ b/content/projects/guessr.md
@@ -0,0 +1,8 @@
++++
+title = "Guessr"
+description = "Аналог GeoGuessr для Казани"
+project_url = "https://guessr.neonxp.ru"
+git_url = "https://git.neonxp.ru/guessr.git/"
++++
+
+Подробности: [/posts/2024-12-12-guessr/](/posts/2024-12-12-guessr/) \ No newline at end of file