|
import os |
|
import random |
|
import json |
|
import uuid |
|
import requests |
|
import base64 |
|
from flask import Flask, jsonify, render_template, request |
|
from dotenv import load_dotenv |
|
|
|
load_dotenv() |
|
|
|
app = Flask(__name__) |
|
app.config['SECRET_KEY'] = os.urandom(24) |
|
|
|
|
|
ZONES_FILE = 'zones.json' |
|
games = {} |
|
zones = { |
|
"easy": [], |
|
"medium": [], |
|
"hard": [] |
|
} |
|
|
|
|
|
def save_zones_to_file(): |
|
with open(ZONES_FILE, 'w') as f: |
|
json.dump(zones, f, indent=4) |
|
|
|
def load_zones_from_file(): |
|
global zones |
|
if os.path.exists(ZONES_FILE): |
|
try: |
|
with open(ZONES_FILE, 'r') as f: |
|
loaded_zones = json.load(f) |
|
|
|
|
|
if not (isinstance(loaded_zones, dict) and all(k in loaded_zones for k in ["easy", "medium", "hard"])): |
|
raise ValueError("Invalid format") |
|
|
|
migrated = False |
|
for difficulty in loaded_zones: |
|
for zone in loaded_zones[difficulty]: |
|
|
|
if 'id' not in zone: |
|
zone['id'] = uuid.uuid4().hex |
|
migrated = True |
|
|
|
zones = loaded_zones |
|
print(zones) |
|
if migrated: |
|
print("Info: Migrated old zone data by adding unique IDs.") |
|
save_zones_to_file() |
|
|
|
except (json.JSONDecodeError, IOError, ValueError): |
|
print(f"Warning: '{ZONES_FILE}' is corrupted or invalid. Recreating with empty zones.") |
|
save_zones_to_file() |
|
else: |
|
|
|
save_zones_to_file() |
|
|
|
|
|
LOCATIONS = [ |
|
{'lat': 48.85824, 'lng': 2.2945}, |
|
{'lat': 40.748440, 'lng': -73.985664}, |
|
{'lat': 35.689487, 'lng': 139.691711}, |
|
{'lat': -33.856784, 'lng': 151.215297} |
|
] |
|
|
|
def generate_game_id(): |
|
return ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10)) |
|
|
|
@app.route('/') |
|
def index(): |
|
google_maps_api_key = os.getenv('GOOGLE_MAPS_API_KEY') |
|
if not google_maps_api_key: |
|
return "Error: GOOGLE_MAPS_API_KEY not set. Please set it in a .env file.", 500 |
|
return render_template('index.html', google_maps_api_key=google_maps_api_key) |
|
|
|
@app.route('/admin') |
|
def admin(): |
|
google_maps_api_key = os.getenv('GOOGLE_MAPS_API_KEY') |
|
if not google_maps_api_key: |
|
return "Error: GOOGLE_MAPS_API_KEY not set. Please set it in a .env file.", 500 |
|
return render_template('admin.html', google_maps_api_key=google_maps_api_key) |
|
|
|
@app.route('/api/zones', methods=['GET', 'POST', 'DELETE']) |
|
def handle_zones(): |
|
if request.method == 'POST': |
|
data = request.json |
|
difficulty = data.get('difficulty') |
|
zone_data = data.get('zone') |
|
if difficulty and zone_data and difficulty in zones: |
|
zone_data['id'] = uuid.uuid4().hex |
|
zones[difficulty].append(zone_data) |
|
save_zones_to_file() |
|
return jsonify({'message': 'Zone saved successfully'}), 201 |
|
return jsonify({'error': 'Invalid data'}), 400 |
|
|
|
if request.method == 'DELETE': |
|
data = request.json |
|
zone_id = data.get('zone_id') |
|
if not zone_id: |
|
return jsonify({'error': 'Zone ID is required'}), 400 |
|
|
|
for difficulty in zones: |
|
zones[difficulty] = [z for z in zones[difficulty] if z.get('id') != zone_id] |
|
|
|
save_zones_to_file() |
|
return jsonify({'message': 'Zone deleted successfully'}) |
|
|
|
|
|
return jsonify(zones) |
|
|
|
@app.route('/start_game', methods=['POST']) |
|
def start_game(): |
|
data = request.json or {} |
|
difficulty = data.get('difficulty', 'easy') |
|
|
|
start_location = None |
|
|
|
if difficulty in zones and zones[difficulty]: |
|
selected_zone_list = zones[difficulty] |
|
print("selected_zone_list", selected_zone_list) |
|
selected_zone = random.choice(selected_zone_list) |
|
print("selected_zone", selected_zone) |
|
|
|
if selected_zone['type'] == 'rectangle': |
|
bounds = selected_zone['bounds'] |
|
north, south, east, west = bounds['north'], bounds['south'], bounds['east'], bounds['west'] |
|
|
|
|
|
if west > east: |
|
east += 360 |
|
|
|
rand_lng = random.uniform(west, east) |
|
if rand_lng > 180: |
|
rand_lng -= 360 |
|
|
|
rand_lat = random.uniform(south, north) |
|
start_location = {'lat': rand_lat, 'lng': rand_lng} |
|
|
|
|
|
if not start_location: |
|
start_location = random.choice(LOCATIONS) |
|
|
|
game_id = generate_game_id() |
|
games[game_id] = { |
|
'start_location': start_location, |
|
'current_location': start_location, |
|
'guesses': [], |
|
'moves': 0, |
|
'actions': [], |
|
'game_over': False |
|
} |
|
|
|
google_maps_api_key = os.getenv('GOOGLE_MAPS_API_KEY') |
|
|
|
|
|
streetview_image = None |
|
compass_heading = random.randint(0, 359) |
|
if google_maps_api_key: |
|
try: |
|
lat, lng = start_location['lat'], start_location['lng'] |
|
streetview_url = f"https://maps.googleapis.com/maps/api/streetview?size=640x400&location={lat},{lng}&heading={compass_heading}&pitch=0&fov=90&key={google_maps_api_key}" |
|
|
|
response = requests.get(streetview_url) |
|
if response.status_code == 200: |
|
|
|
streetview_image = base64.b64encode(response.content).decode('utf-8') |
|
except Exception as e: |
|
print(f"Error fetching Street View image: {e}") |
|
|
|
return jsonify({ |
|
'game_id': game_id, |
|
'streetview_image': streetview_image, |
|
'compass_heading': compass_heading |
|
}) |
|
|
|
@app.route('/game/<game_id>/state', methods=['GET']) |
|
def get_game_state(game_id): |
|
game = games.get(game_id) |
|
if not game: |
|
return jsonify({'error': 'Game not found'}), 404 |
|
return jsonify(game) |
|
|
|
@app.route('/game/<game_id>/move', methods=['POST']) |
|
def move(game_id): |
|
game = games.get(game_id) |
|
if not game: |
|
return jsonify({'error': 'Game not found'}), 404 |
|
if game['game_over']: |
|
return jsonify({'error': 'Game is over'}), 400 |
|
|
|
data = request.json |
|
new_lat = data.get('lat') |
|
new_lng = data.get('lng') |
|
|
|
if new_lat is None or new_lng is None: |
|
return jsonify({'error': 'Missing lat/lng for move'}), 400 |
|
|
|
game['current_location'] = {'lat': new_lat, 'lng': new_lng} |
|
game['moves'] += 1 |
|
game['actions'].append({'type': 'move', 'location': {'lat': new_lat, 'lng': new_lng}}) |
|
|
|
|
|
google_maps_api_key = os.getenv('GOOGLE_MAPS_API_KEY') |
|
streetview_image = None |
|
compass_heading = random.randint(0, 359) |
|
if google_maps_api_key: |
|
try: |
|
streetview_url = f"https://maps.googleapis.com/maps/api/streetview?size=640x400&location={new_lat},{new_lng}&heading={compass_heading}&pitch=0&fov=90&key={google_maps_api_key}" |
|
|
|
response = requests.get(streetview_url) |
|
if response.status_code == 200: |
|
|
|
streetview_image = base64.b64encode(response.content).decode('utf-8') |
|
except Exception as e: |
|
print(f"Error fetching Street View image: {e}") |
|
|
|
return jsonify({ |
|
'message': 'Move successful', |
|
'streetview_image': streetview_image, |
|
'compass_heading': compass_heading |
|
}) |
|
|
|
@app.route('/game/<game_id>/guess', methods=['POST']) |
|
def guess(game_id): |
|
game = games.get(game_id) |
|
if not game: |
|
return jsonify({'error': 'Game not found'}), 404 |
|
if game['game_over']: |
|
return jsonify({'error': 'Game is over'}), 400 |
|
|
|
data = request.json |
|
guess_lat = data.get('lat') |
|
guess_lng = data.get('lng') |
|
|
|
if guess_lat is None or guess_lng is None: |
|
return jsonify({'error': 'Missing lat/lng for guess'}), 400 |
|
|
|
guess_location = {'lat': guess_lat, 'lng': guess_lng} |
|
game['guesses'].append(guess_location) |
|
|
|
|
|
|
|
from math import radians, cos, sin, asin, sqrt |
|
def haversine(lat1, lon1, lat2, lon2): |
|
|
|
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) |
|
|
|
dlon = lon2 - lon1 |
|
dlat = lat2 - lat1 |
|
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2 |
|
c = 2 * asin(sqrt(a)) |
|
r = 6371 |
|
return c * r |
|
|
|
distance = haversine( |
|
game['start_location']['lat'], game['start_location']['lng'], |
|
guess_lat, guess_lng |
|
) |
|
|
|
|
|
max_score = 5000 |
|
score = max(0, max_score - distance) |
|
|
|
game['actions'].append({ |
|
'type': 'guess', |
|
'location': guess_location, |
|
'result': { |
|
'distance_km': distance, |
|
'score': score |
|
} |
|
}) |
|
game['game_over'] = True |
|
|
|
return jsonify({ |
|
'message': 'Guess received', |
|
'guess_location': guess_location, |
|
'actual_location': game['start_location'], |
|
'distance_km': distance, |
|
'score': score |
|
}) |
|
|
|
|
|
load_zones_from_file() |
|
|
|
if __name__ == '__main__': |
|
app.run(debug=True) |