Download main.py from Unmid/yt: direct link, hf CLI and curl.
- Browser
- Download file 17.6 kB
-
https://huggingface.co/spaces/Unmid/yt/resolve/main/main.py
- Command line
-
hf download hf://spaces/Unmid/yt/main.py
-
curl -L -o main.py https://huggingface.co/spaces/Unmid/yt/resolve/main/main.py
17.6 kB
| import os | |
| import json | |
| import asyncio | |
| import math | |
| import uuid | |
| import random | |
| import time | |
| from collections import defaultdict | |
| from aiohttp import web, ClientSession | |
| import socketio | |
| # --- CONFIGURATION --- | |
| PORT = int(os.getenv('SERVER_PORT', 8080)) | |
| ADMIN_PATH = os.getenv('ADMIN_PATH', '/secret-admin-panel') | |
| # !!! REPLACE WITH YOUR DISCORD APP DETAILS !!! | |
| DISCORD_CLIENT_ID = "1450064181972832409" | |
| DISCORD_CLIENT_SECRET = "F1Tm1JL5sDmbOn9RGT-qYKyc7Q0_9IsW" | |
| DISCORD_REDIRECT_URI = "https://orbitmc-yt.hf.space/callback" | |
| GAME_TICK_RATE = 30 | |
| GRID_SIZE = 8 | |
| BLOCK_SIZE = 280 | |
| ROAD_WIDTH = 90 | |
| CELL_SIZE = BLOCK_SIZE + ROAD_WIDTH | |
| MAP_WIDTH = GRID_SIZE * CELL_SIZE | |
| MAP_HEIGHT = GRID_SIZE * CELL_SIZE | |
| # --- ASSET DEFINITIONS --- | |
| ENTITY_TYPES = { | |
| 'ROCK': {'r': 8, 'h': 8, 'color': '#7f8c8d', 'score': 1, 'type': 'rock'}, | |
| 'FLOWER': {'r': 6, 'h': 4, 'color': '#e84393', 'score': 1, 'type': 'flower'}, | |
| 'FENCE': {'w': 20, 'l': 4, 'h': 10, 'color': '#8e44ad', 'score': 1, 'type': 'building'}, | |
| 'HYDRANT': {'r': 6, 'h': 12, 'color': '#e74c3c', 'sideColor': '#c0392b', 'score': 1, 'type': 'cylinder'}, | |
| 'POST': {'r': 4, 'h': 40, 'color': '#bdc3c7', 'sideColor': '#7f8c8d', 'score': 2, 'type': 'cylinder'}, | |
| 'BUSH': {'r': 12, 'h': 10, 'color': '#2ecc71', 'score': 3, 'type': 'bush'}, | |
| 'TREE': {'r': 18, 'h': 70, 'color': '#27ae60', 'score': 5, 'type': 'tree'}, | |
| 'CAR': {'w': 48, 'l': 24, 'h': 12, 'cabinW': 28, 'cabinL': 20, 'cabinH': 10, 'color': '#e74c3c', 'score': 10, 'type': 'car', 'mobile': True}, | |
| 'HOUSE_S': {'w': 70, 'l': 70, 'h': 60, 'color': '#e67e22', 'sideColor': '#d35400', 'score': 50, 'type': 'building', 'windows': True}, | |
| 'HOUSE_M': {'w': 100, 'l': 90, 'h': 90, 'color': '#3498db', 'sideColor': '#2980b9', 'score': 100, 'type': 'building', 'windows': True}, | |
| 'OFFICE': {'w': 110, 'l': 110, 'h': 160, 'color': '#9b59b6', 'sideColor': '#8e44ad', 'score': 200, 'type': 'building', 'windows': True}, | |
| 'TOWER': {'w': 130, 'l': 130, 'h': 280, 'color': '#34495e', 'sideColor': '#2c3e50', 'score': 300, 'type': 'building', 'windows': True} | |
| } | |
| sio = socketio.AsyncServer(async_mode='aiohttp', cors_allowed_origins='*') | |
| app = web.Application() | |
| sio.attach(app) | |
| class GameState: | |
| def __init__(self): | |
| self.players = {} | |
| self.sockets = {} | |
| self.static_entities = [] | |
| # Spatial Hash Grid for Static Entities: key=(gridX, gridY), val=[entities] | |
| self.static_grid = defaultdict(list) | |
| self.dynamic_entities = [] | |
| self.removed_entities = [] | |
| self.active = True | |
| self.user_data_file = 'user_data.json' | |
| self.persistent_data = self.load_data() | |
| self.generate_map() | |
| def load_data(self): | |
| if os.path.exists(self.user_data_file): | |
| try: | |
| with open(self.user_data_file, 'r') as f: return json.load(f) | |
| except: return {} | |
| return {} | |
| def save_data(self): | |
| with open(self.user_data_file, 'w') as f: | |
| json.dump(self.persistent_data, f, indent=2) | |
| def generate_map(self): | |
| self.static_entities = [] | |
| self.static_grid.clear() | |
| self.dynamic_entities = [] | |
| # Grid Generation | |
| for ix in range(GRID_SIZE + 1): | |
| for iy in range(GRID_SIZE + 1): | |
| x = ix * CELL_SIZE | |
| y = iy * CELL_SIZE | |
| if ix < GRID_SIZE and iy < GRID_SIZE: | |
| self.generate_block(x + ROAD_WIDTH/2, y + ROAD_WIDTH/2) | |
| # Car Generation | |
| lane_offset = ROAD_WIDTH / 4 | |
| for _ in range(60): | |
| self.spawn_car(lane_offset) | |
| def add_entity(self, x, y, template, **kwargs): | |
| ent = template.copy() | |
| ent.update(kwargs) | |
| ent['x'] = x | |
| ent['y'] = y | |
| ent['id'] = str(uuid.uuid4()) | |
| ent['startX'] = x | |
| ent['startY'] = y | |
| ent['varSeed'] = random.random() | |
| ent['rotation'] = kwargs.get('rotation', 0) | |
| if ent['type'] in ['building', 'car']: | |
| ent['radius'] = math.hypot(ent['w']/2, ent.get('l', ent['w'])/2) | |
| else: | |
| ent['radius'] = ent['r'] | |
| if ent['type'] == 'flower': | |
| ent['color'] = random.choice(['#e84393', '#fd79a8', '#00b894', '#fab1a0']) | |
| # Add to main list AND spatial grid | |
| self.static_entities.append(ent) | |
| gx = int(x // CELL_SIZE) | |
| gy = int(y // CELL_SIZE) | |
| self.static_grid[(gx, gy)].append(ent) | |
| def get_nearby_static(self, x, y): | |
| # Return entities in the 3x3 grid cells around the point | |
| gx = int(x // CELL_SIZE) | |
| gy = int(y // CELL_SIZE) | |
| nearby = [] | |
| for ix in range(gx - 1, gx + 2): | |
| for iy in range(gy - 1, gy + 2): | |
| nearby.extend(self.static_grid[(ix, iy)]) | |
| return nearby | |
| def generate_block(self, bx, by): | |
| cx = bx + BLOCK_SIZE/2 | |
| cy = by + BLOCK_SIZE/2 | |
| b_type = random.random() | |
| if b_type < 0.2: # Park | |
| inset = 10 | |
| sz = BLOCK_SIZE - inset*2 | |
| for k in range(0, int(sz), 25): | |
| self.add_entity(bx + inset + k, by + inset, ENTITY_TYPES['FENCE']) | |
| self.add_entity(bx + inset + k, by + BLOCK_SIZE - inset, ENTITY_TYPES['FENCE']) | |
| for k in range(0, int(sz), 25): | |
| self.add_entity(bx + inset, by + inset + k, ENTITY_TYPES['FENCE'], w=5, l=20) | |
| self.add_entity(bx + BLOCK_SIZE - inset, by + inset + k, ENTITY_TYPES['FENCE'], w=5, l=20) | |
| for _ in range(10): | |
| ox = random.random() * (sz-40) + 20 | |
| oy = random.random() * (sz-40) + 20 | |
| tmpl = ENTITY_TYPES['TREE'] if random.random() > 0.6 else ENTITY_TYPES['FLOWER'] | |
| self.add_entity(bx+inset+ox, by+inset+oy, tmpl) | |
| self.add_entity(cx, cy, ENTITY_TYPES['HYDRANT']) | |
| elif b_type < 0.6: # Residential | |
| q = BLOCK_SIZE/4 | |
| self.add_entity(cx - q, cy - q, ENTITY_TYPES['HOUSE_S']) | |
| self.add_entity(cx + q, cy - q, ENTITY_TYPES['HOUSE_M']) | |
| self.add_entity(cx - q, cy + q, ENTITY_TYPES['HOUSE_M']) | |
| self.add_entity(cx + q, cy + q, ENTITY_TYPES['HOUSE_S']) | |
| self.add_entity(cx, cy, ENTITY_TYPES['TREE']) | |
| self.add_entity(bx + 10, cy, ENTITY_TYPES['BUSH']) | |
| self.add_entity(bx + BLOCK_SIZE - 10, cy, ENTITY_TYPES['BUSH']) | |
| else: # Downtown | |
| if random.random() > 0.5: | |
| self.add_entity(cx, cy, ENTITY_TYPES['TOWER']) | |
| else: | |
| self.add_entity(cx - 50, cy, ENTITY_TYPES['OFFICE']) | |
| self.add_entity(cx + 50, cy, ENTITY_TYPES['OFFICE']) | |
| self.add_entity(bx + 20, by + 20, ENTITY_TYPES['POST']) | |
| self.add_entity(bx + BLOCK_SIZE - 20, by + 20, ENTITY_TYPES['POST']) | |
| self.add_entity(bx + 20, by + BLOCK_SIZE - 20, ENTITY_TYPES['POST']) | |
| self.add_entity(bx + BLOCK_SIZE - 20, by + BLOCK_SIZE - 20, ENTITY_TYPES['POST']) | |
| def spawn_car(self, offset): | |
| orient = 0 if random.random() > 0.5 else 1 | |
| if orient == 0: | |
| iy = random.randint(0, GRID_SIZE) | |
| y = iy * CELL_SIZE | |
| x = random.random() * MAP_WIDTH | |
| dir = 1 if random.random() > 0.5 else -1 | |
| y += dir * offset | |
| rot = 0 if dir == 1 else math.pi | |
| else: | |
| ix = random.randint(0, GRID_SIZE) | |
| x = ix * CELL_SIZE | |
| y = random.random() * MAP_HEIGHT | |
| dir = 1 if random.random() > 0.5 else -1 | |
| x += dir * offset | |
| rot = math.pi/2 if dir == 1 else -math.pi/2 | |
| car = ENTITY_TYPES['CAR'].copy() | |
| car.update({ | |
| 'x': x, 'y': y, 'rotation': rot, 'id': str(uuid.uuid4()), | |
| 'radius': math.hypot(24, 12), 'type': 'car', 'mobile': True, | |
| 'color': random.choice(['#e74c3c', '#3498db', '#f1c40f', '#ecf0f1', '#2c3e50']) | |
| }) | |
| self.dynamic_entities.append(car) | |
| def add_player(self, session_id, user_info): | |
| saved = self.persistent_data.get(session_id, {}) | |
| self.players[session_id] = { | |
| 'id': session_id, | |
| 'discord_id': user_info['id'], | |
| 'username': user_info['username'], | |
| 'avatar': f"https://cdn.discordapp.com/avatars/{user_info['id']}/{user_info['avatar']}.png" if user_info['avatar'] else "", | |
| 'x': random.random() * MAP_WIDTH, | |
| 'y': random.random() * MAP_HEIGHT, | |
| 'r': saved.get('r', 35), | |
| 'target_r': saved.get('r', 35), | |
| 'score': saved.get('score', 0), | |
| 'color': '#3498db', | |
| 'input_angle': 0, 'input_force': 0, 'vx': 0, 'vy': 0, | |
| 'alive': True, 'respawn_timer': 0 | |
| } | |
| if session_id not in self.persistent_data: | |
| self.persistent_data[session_id] = {'r': 35, 'score': 0, 'username': user_info['username']} | |
| self.save_data() | |
| def update(self, dt): | |
| if not self.active: return | |
| self.removed_entities = [] | |
| sorted_players = sorted(self.players.values(), key=lambda p: p['r'], reverse=True) | |
| for p in sorted_players: | |
| if not p['alive']: | |
| p['respawn_timer'] -= dt | |
| if p['respawn_timer'] <= 0: | |
| p['alive'] = True | |
| p['r'] = 35; p['target_r'] = 35; p['score'] = 0 | |
| p['x'] = random.random() * MAP_WIDTH | |
| p['y'] = random.random() * MAP_HEIGHT | |
| continue | |
| speed = 180 * (1 - (p['r'] / 800)) | |
| p['vx'] = math.cos(p['input_angle']) * p['input_force'] * speed | |
| p['vy'] = math.sin(p['input_angle']) * p['input_force'] * speed | |
| p['x'] = max(p['r'], min(MAP_WIDTH - p['r'], p['x'] + p['vx'] * dt)) | |
| p['y'] = max(p['r'], min(MAP_HEIGHT - p['r'], p['y'] + p['vy'] * dt)) | |
| if p['r'] < p['target_r']: p['r'] += (p['target_r'] - p['r']) * dt | |
| # Eat Static (OPTIMIZED: Use Spatial Hash) | |
| nearby_static = self.get_nearby_static(p['x'], p['y']) | |
| for ent in nearby_static: | |
| # Basic check before expensive sqrt | |
| if abs(p['x'] - ent['x']) > p['r'] or abs(p['y'] - ent['y']) > p['r']: continue | |
| dist = math.hypot(p['x'] - ent['x'], p['y'] - ent['y']) | |
| if dist < p['r'] and p['r'] > ent['radius']: | |
| self.static_entities.remove(ent) | |
| # Also remove from grid for consistency (slow, but eats are rare compared to frames) | |
| gx, gy = int(ent['x']//CELL_SIZE), int(ent['y']//CELL_SIZE) | |
| if ent in self.static_grid[(gx, gy)]: | |
| self.static_grid[(gx, gy)].remove(ent) | |
| self.removed_entities.append(ent['id']) | |
| p['score'] += ent['score'] | |
| p['target_r'] += math.sqrt(ent['score']) * 0.3 | |
| # Eat Cars | |
| for car in self.dynamic_entities: | |
| dist = math.hypot(p['x'] - car['x'], p['y'] - car['y']) | |
| if dist < p['r'] and p['r'] > car['radius']: | |
| p['score'] += car['score'] | |
| p['target_r'] += math.sqrt(car['score']) * 0.3 | |
| car['x'] = random.randint(0, GRID_SIZE) * CELL_SIZE | |
| car['y'] = random.random() * MAP_HEIGHT | |
| self.removed_entities.append(car['id']) | |
| # Eat Players | |
| for other in sorted_players: | |
| if p == other or not other['alive']: continue | |
| dist = math.hypot(p['x'] - other['x'], p['y'] - other['y']) | |
| if p['r'] > other['r'] * 1.1 and dist < p['r'] - other['r']*0.4: | |
| other['alive'] = False | |
| other['respawn_timer'] = 5 | |
| p['score'] += 50 + other['score'] * 0.5 | |
| p['target_r'] += 5 | |
| self.persistent_data[p['id']]['score'] = p['score'] | |
| self.persistent_data[p['id']]['r'] = p['target_r'] | |
| self.persistent_data[other['id']]['score'] = 0 | |
| self.persistent_data[other['id']]['r'] = 35 | |
| self.save_data() | |
| # Update Cars | |
| for car in self.dynamic_entities: | |
| speed = 150 | |
| car['x'] += math.cos(car['rotation']) * speed * dt | |
| car['y'] += math.sin(car['rotation']) * speed * dt | |
| if car['x'] > MAP_WIDTH: car['x'] = 0 | |
| if car['x'] < 0: car['x'] = MAP_WIDTH | |
| if car['y'] > MAP_HEIGHT: car['y'] = 0 | |
| if car['y'] < 0: car['y'] = MAP_HEIGHT | |
| game = GameState() | |
| # --- WEB HANDLERS --- | |
| async def handle_index(request): return web.FileResponse('./index.html') | |
| async def handle_admin(request): return web.FileResponse('./admin.html') | |
| async def handle_login(request): | |
| return web.HTTPFound(f"https://discord.com/api/oauth2/authorize?client_id={DISCORD_CLIENT_ID}&redirect_uri={DISCORD_REDIRECT_URI}&response_type=code&scope=identify") | |
| async def handle_callback(request): | |
| code = request.query.get('code') | |
| if not code: return web.Response(text="No code") | |
| async with ClientSession() as s: | |
| data = {'client_id': DISCORD_CLIENT_ID, 'client_secret': DISCORD_CLIENT_SECRET, 'grant_type': 'authorization_code', 'code': code, 'redirect_uri': DISCORD_REDIRECT_URI} | |
| async with s.post('https://discord.com/api/oauth2/token', data=data) as r: | |
| token_data = await r.json() | |
| if 'access_token' not in token_data: return web.Response(text="Auth failed") | |
| headers = {'Authorization': f"Bearer {token_data['access_token']}"} | |
| async with s.get('https://discord.com/api/users/@me', headers=headers) as r: | |
| user_data = await r.json() | |
| sid = str(uuid.uuid4()) | |
| game.add_player(sid, user_data) | |
| # Broadcast new player metadata to everyone | |
| p = game.players[sid] | |
| meta = {'id': p['id'], 'username': p['username'], 'avatar': p['avatar'], 'color': p['color']} | |
| await sio.emit('player_joined', meta) | |
| return web.HTTPFound(f"/?token={sid}") | |
| app.router.add_get('/', handle_index) | |
| app.router.add_get('/login', handle_login) | |
| app.router.add_get('/callback', handle_callback) | |
| app.router.add_get(ADMIN_PATH, handle_admin) | |
| async def connect(sid, environ, auth): | |
| token = auth.get('token') if auth else None | |
| if not token or token not in game.players: return False | |
| game.sockets[sid] = token | |
| # Send Static Info (Map + All Player Metadata) | |
| all_players_meta = [ | |
| {'id': p['id'], 'username': p['username'], 'avatar': p['avatar'], 'color': p['color']} | |
| for p in game.players.values() | |
| ] | |
| await sio.emit('init_game', { | |
| 'width': MAP_WIDTH, 'height': MAP_HEIGHT, | |
| 'static_entities': game.static_entities, | |
| 'players_meta': all_players_meta | |
| }, to=sid) | |
| async def disconnect(sid): | |
| if sid in game.sockets: | |
| pid = game.sockets[sid] | |
| del game.sockets[sid] | |
| await sio.emit('player_left', {'id': pid}) | |
| async def input_data(sid, data): | |
| if sid in game.sockets: | |
| p = game.players[game.sockets[sid]] | |
| p['input_angle'] = data.get('angle', 0) | |
| p['input_force'] = min(max(data.get('force', 0), 0), 1) | |
| async def admin_cmd(sid, data): | |
| cmd = data.get('cmd') | |
| if cmd == 'start_tournament': | |
| game.active = True | |
| game.generate_map() | |
| for p in game.players.values(): | |
| p['r'] = 35; p['target_r'] = 35; p['score'] = 0; p['alive'] = True | |
| p['x'] = random.random() * MAP_WIDTH; p['y'] = random.random() * MAP_HEIGHT | |
| await sio.emit('tournament_reset', {'static_entities': game.static_entities}) | |
| elif cmd == 'stop_tournament': | |
| game.active = False | |
| res = [{'username': p['username'], 'score': int(p['score']), 'avatar': p['avatar']} for p in game.players.values()] | |
| await sio.emit('tournament_end', {'results': sorted(res, key=lambda x: x['score'], reverse=True)}) | |
| async def game_loop(): | |
| while True: | |
| start = time.time() | |
| if game.active: | |
| game.update(1/GAME_TICK_RATE) | |
| # OPTIMIZED PACKET: Strip static data (avatar, username, color, car dimensions) | |
| # Only send dynamic data (id, x, y, r, score, alive) | |
| state = { | |
| 'players': [ | |
| {'id': p['id'], 'x': int(p['x']), 'y': int(p['y']), 'r': int(p['r']), | |
| 'alive': p['alive'], 'score': int(p['score'])} | |
| for p in game.players.values() if p['alive'] | |
| ], | |
| 'cars': [ | |
| {'id': c['id'], 'x': int(c['x']), 'y': int(c['y']), 'rotation': round(c['rotation'], 2), | |
| 'color': c['color'], 'w': c['w'], 'l': c['l'], 'h': c['h'], # Keep car static for now as they respawn often | |
| 'cabinW': c.get('cabinW'), 'cabinL': c.get('cabinL'), 'type': 'car'} | |
| for c in game.dynamic_entities | |
| ], | |
| 'removed': game.removed_entities | |
| } | |
| await sio.emit('game_update', state) | |
| await asyncio.sleep(max(0, (1/GAME_TICK_RATE) - (time.time() - start))) | |
| async def init_app(): | |
| asyncio.create_task(game_loop()) | |
| return app | |
| if __name__ == '__main__': | |
| web.run_app(init_app(), port=PORT) |