File size: 9,936 Bytes
8398097 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
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)
# In-memory "database"
ZONES_FILE = 'zones.json'
games = {}
zones = {
"easy": [],
"medium": [],
"hard": []
}
# --- Zone Persistence Functions ---
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)
# Basic format validation
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]:
# Assign ID if missing
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() # This creates a fresh, empty, and valid file
else:
# If file doesn't exist, create an empty one.
save_zones_to_file()
# Predefined locations for the game, used as a fallback
LOCATIONS = [
{'lat': 48.85824, 'lng': 2.2945}, # Eiffel Tower, Paris
{'lat': 40.748440, 'lng': -73.985664}, # Empire State Building, New York
{'lat': 35.689487, 'lng': 139.691711}, # Tokyo, Japan
{'lat': -33.856784, 'lng': 151.215297} # Sydney Opera House, Australia
]
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'})
# GET request
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']
# Handle antimeridian crossing
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}
# Fallback to predefined locations if no zones are defined
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')
# Fetch Street View image
streetview_image = None
compass_heading = random.randint(0, 359) # Random compass direction
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:
# Convert image to base64
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}})
# Fetch Street View image for the new location
google_maps_api_key = os.getenv('GOOGLE_MAPS_API_KEY')
streetview_image = None
compass_heading = random.randint(0, 359) # Random compass direction
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:
# Convert image to base64
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)
# Calculate score (simple distance for now)
# This is a placeholder for a more complex scoring function.
from math import radians, cos, sin, asin, sqrt
def haversine(lat1, lon1, lat2, lon2):
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])
# haversine formula
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 # Radius of earth in kilometers.
return c * r
distance = haversine(
game['start_location']['lat'], game['start_location']['lng'],
guess_lat, guess_lng
)
# Simple scoring
max_score = 5000
score = max(0, max_score - distance) # The closer, the higher the score
game['actions'].append({
'type': 'guess',
'location': guess_location,
'result': {
'distance_km': distance,
'score': score
}
})
game['game_over'] = True # For now, one guess ends the game.
return jsonify({
'message': 'Guess received',
'guess_location': guess_location,
'actual_location': game['start_location'],
'distance_km': distance,
'score': score
})
# Load zones at startup
load_zones_from_file()
if __name__ == '__main__':
app.run(debug=True) |