405 lines
15 KiB
Python
405 lines
15 KiB
Python
import json
|
|
import random
|
|
import string
|
|
import uuid
|
|
import os
|
|
from django.shortcuts import render, redirect, get_object_or_404
|
|
from django.contrib import messages
|
|
from django.utils import timezone
|
|
from datetime import timedelta
|
|
from django.http import JsonResponse
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
from django.core.management import call_command
|
|
from .models import GameProject, UserSession, AdminConfig, RentalOption, UserPurchase
|
|
from ai.local_ai_api import LocalAIApi
|
|
from .pexels import fetch_first
|
|
|
|
# Helper to check authentication
|
|
def get_auth_status(request):
|
|
is_admin = request.session.get('is_admin', False)
|
|
user_code = request.session.get('user_code')
|
|
user = None
|
|
if user_code:
|
|
user = UserSession.objects.filter(access_code=user_code).first()
|
|
return is_admin, user
|
|
|
|
def index(request):
|
|
is_admin, user = get_auth_status(request)
|
|
if is_admin:
|
|
return redirect('admin_dashboard')
|
|
if user:
|
|
return redirect('catalog')
|
|
return render(request, 'core/index.html', {'title': 'Acesso'})
|
|
|
|
def admin_login(request):
|
|
if request.method == 'POST':
|
|
key = request.POST.get('private_key')
|
|
if AdminConfig.objects.filter(private_key=key).exists():
|
|
request.session['is_admin'] = True
|
|
request.session['user_code'] = None
|
|
return redirect('admin_dashboard')
|
|
messages.error(request, 'Chave Privada Inválida.')
|
|
return render(request, 'core/admin_login.html', {'title': 'Admin Login'})
|
|
|
|
def generate_code(request):
|
|
if request.method == 'POST':
|
|
phone = request.POST.get('phone')
|
|
code = ''.join(random.choices(string.digits, k=6))
|
|
while UserSession.objects.filter(access_code=code).exists():
|
|
code = ''.join(random.choices(string.digits, k=6))
|
|
|
|
UserSession.objects.create(access_code=code, phone_number=phone)
|
|
request.session['user_code'] = code
|
|
request.session['is_admin'] = False
|
|
messages.success(request, f'Seu código de acesso é: {code}. Guarde-o bem!')
|
|
return redirect('catalog')
|
|
return redirect('index')
|
|
|
|
def recover_code(request):
|
|
if request.method == 'POST':
|
|
phone = request.POST.get('phone')
|
|
user = UserSession.objects.filter(phone_number=phone).first()
|
|
if user:
|
|
request.session['user_code'] = user.access_code
|
|
messages.success(request, f'Bem-vindo de volta! Seu código {user.access_code} foi recuperado.')
|
|
return redirect('catalog')
|
|
messages.error(request, 'Nenhum código encontrado para este telefone.')
|
|
return redirect('index')
|
|
|
|
def logout(request):
|
|
request.session.flush()
|
|
return redirect('index')
|
|
|
|
# AI Helper - Autonomously generates full games with story and code
|
|
def generate_game_script(title, prompt="", genre="arcade"):
|
|
system_prompt = (
|
|
"You are the Ultimate Autonomous AI Game Developer and Storyteller. Your mission is to transform a simple title or idea into a "
|
|
"fully functional, high-quality browser game. You are responsible for the entire development lifecycle:\n"
|
|
"1. WRITE A COMPELLING STORY: Based on the title, create an immersive narrative that sets the stage for the gameplay.\n"
|
|
"2. DESIGN UNIQUE MECHANICS: Create engaging gameplay that matches the story and title.\n"
|
|
"3. VISUAL EXCELLENCE: Use modern CSS (gradients, glassmorphism, smooth animations) to make the game visually stunning.\n"
|
|
"4. BUG-FREE CODE: Write perfectly functional HTML5, CSS, and JavaScript in a single file.\n"
|
|
"5. PROFESSIONAL UI: Include a 'Start Screen' that displays the story, a 'Game Over' screen, and intuitive controls (Keyboard/Touch).\n"
|
|
"6. AUTONOMOUS CORRECTION: You MUST review your own logic and fix any potential bugs or syntax errors before providing the final code.\n"
|
|
"Return ONLY the complete HTML5 code starting with <!DOCTYPE html>."
|
|
)
|
|
|
|
full_query = f"Game Title: {title}. Description/Idea: {prompt}. Genre: {genre}."
|
|
user_prompt = f"Act as an autonomous developer. Develop a complete browser game (Story + Code) for: {full_query}. Ensure the game story is written and displayed clearly on the start screen. Use Portuguese (Brazil) for all in-game text, story, and instructions."
|
|
|
|
# Step 1: Initial Generation
|
|
response = LocalAIApi.create_response({
|
|
"input": [
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": user_prompt},
|
|
],
|
|
})
|
|
|
|
if not response.get("success"):
|
|
return None
|
|
|
|
code = LocalAIApi.extract_text(response)
|
|
|
|
# Step 2: Autonomous Bug Correction
|
|
correction_prompt = (
|
|
"Review the HTML/JS code below. Ensure it is fully functional, has no syntax errors, and the game loop works correctly. "
|
|
"Fix any issues and return the final, polished HTML5 code. ONLY RETURN THE CODE.\n\n"
|
|
f"CODE TO REVIEW:\n{code}"
|
|
)
|
|
|
|
corrected_response = LocalAIApi.create_response({
|
|
"input": [
|
|
{"role": "system", "content": "You are a senior debugger and optimizer. Your goal is to ensure the code is 100% bug-free and optimized."},
|
|
{"role": "user", "content": correction_prompt},
|
|
],
|
|
})
|
|
|
|
if corrected_response.get("success"):
|
|
final_code = LocalAIApi.extract_text(corrected_response)
|
|
if "<!DOCTYPE" in final_code:
|
|
return final_code
|
|
return code
|
|
|
|
return code
|
|
|
|
def generate_game_story(title, genre="arcade"):
|
|
"""Generates only the story/description for a game."""
|
|
system_prompt = "You are a creative writer for video games. Write a short, immersive story/description for a game."
|
|
user_prompt = f"Write a compelling 2-3 paragraph story in Portuguese (Brazil) for a game titled '{title}' in the {genre} genre."
|
|
|
|
response = LocalAIApi.create_response({
|
|
"input": [
|
|
{"role": "system", "content": system_prompt},
|
|
{"role": "user", "content": user_prompt},
|
|
],
|
|
})
|
|
|
|
if response.get("success"):
|
|
return LocalAIApi.extract_text(response)
|
|
return ""
|
|
|
|
# ADMIN VIEWS
|
|
def admin_dashboard(request):
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
projects = GameProject.objects.all().order_by('-created_at')
|
|
rental_options = RentalOption.objects.all()
|
|
|
|
return render(request, 'core/admin_dashboard.html', {
|
|
'projects': projects,
|
|
'rental_options': rental_options,
|
|
'title': 'Painel do Administrador'
|
|
})
|
|
|
|
def admin_create_game(request):
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
if request.method == 'POST':
|
|
title = request.POST.get('title')
|
|
prompt = request.POST.get('prompt', '')
|
|
genre = request.POST.get('genre', 'arcade')
|
|
external_url = request.POST.get('external_url', '')
|
|
image = request.FILES.get('image')
|
|
use_ai = request.POST.get('use_ai') == 'on' or True
|
|
manual_script = request.POST.get('script_code', '')
|
|
|
|
description = prompt
|
|
if not description:
|
|
description = generate_game_story(title, genre)
|
|
|
|
script = manual_script
|
|
if use_ai and not manual_script:
|
|
ai_script = generate_game_script(title, description, genre)
|
|
if ai_script:
|
|
script = ai_script
|
|
else:
|
|
messages.error(request, 'Falha ao gerar o script com AI.')
|
|
|
|
game = GameProject.objects.create(
|
|
title=title,
|
|
description=description,
|
|
prompt=prompt,
|
|
genre=genre,
|
|
image_reference=image,
|
|
script_code=script,
|
|
external_url=external_url,
|
|
is_active=True,
|
|
config_json={"player": {"speed": 200}, "entities": []}
|
|
)
|
|
|
|
if not image:
|
|
pexels_data = fetch_first(f"{title} game background")
|
|
if pexels_data:
|
|
game.image_reference = pexels_data['local_path'].replace('media/', '')
|
|
game.save()
|
|
|
|
messages.success(request, f'Jogo "{title}" criado e ativado com sucesso!')
|
|
return redirect('admin_dashboard')
|
|
|
|
return render(request, 'core/admin_game_form.html', {'title': 'Criar Novo Jogo'})
|
|
|
|
def admin_edit_game(request, pk):
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
project = get_object_or_404(GameProject, pk=pk)
|
|
|
|
if request.method == 'POST':
|
|
project.title = request.POST.get('title')
|
|
project.prompt = request.POST.get('prompt', '')
|
|
project.genre = request.POST.get('genre')
|
|
project.external_url = request.POST.get('external_url', '')
|
|
project.is_active = True
|
|
|
|
if request.FILES.get('image'):
|
|
project.image_reference = request.FILES.get('image')
|
|
|
|
use_ai = request.POST.get('use_ai') == 'on'
|
|
manual_script = request.POST.get('script_code', '')
|
|
|
|
if not project.description:
|
|
project.description = generate_game_story(project.title, project.genre)
|
|
|
|
if use_ai:
|
|
ai_script = generate_game_script(project.title, project.description, project.genre)
|
|
if ai_script:
|
|
project.script_code = ai_script
|
|
else:
|
|
project.script_code = manual_script
|
|
|
|
if not project.image_reference:
|
|
pexels_data = fetch_first(f"{project.title} game background")
|
|
if pexels_data:
|
|
project.image_reference = pexels_data['local_path'].replace('media/', '')
|
|
|
|
project.save()
|
|
messages.success(request, f'Jogo "{project.title}" atualizado e ativado!')
|
|
return redirect('admin_dashboard')
|
|
|
|
return render(request, 'core/admin_game_form.html', {'project': project, 'title': 'Editar Jogo'})
|
|
|
|
def admin_batch_update_ai(request):
|
|
"""Triggers the management command to update all games with AI."""
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
try:
|
|
call_command('update_all_games_ai')
|
|
messages.success(request, 'Todos os jogos foram atualizados com IA!')
|
|
except Exception as e:
|
|
messages.error(request, f'Erro ao atualizar jogos: {str(e)}')
|
|
|
|
return redirect('admin_dashboard')
|
|
|
|
def admin_delete_game(request, pk):
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
project = get_object_or_404(GameProject, pk=pk)
|
|
project.delete()
|
|
messages.success(request, 'Jogo excluído.')
|
|
return redirect('admin_dashboard')
|
|
|
|
def admin_edit_rental(request, pk):
|
|
is_admin, _ = get_auth_status(request)
|
|
if not is_admin:
|
|
return redirect('admin_login')
|
|
|
|
option = get_object_or_404(RentalOption, pk=pk)
|
|
if request.method == 'POST':
|
|
option.title = request.POST.get('title')
|
|
option.price = request.POST.get('price')
|
|
option.duration_days = request.POST.get('duration_days')
|
|
if request.FILES.get('qr1'):
|
|
option.qr_code_1 = request.FILES.get('qr1')
|
|
if request.FILES.get('qr2'):
|
|
option.qr_code_2 = request.FILES.get('qr2')
|
|
option.save()
|
|
messages.success(request, 'Opção de aluguel atualizada.')
|
|
return redirect('admin_dashboard')
|
|
|
|
return render(request, 'core/admin_rental_form.html', {'option': option, 'title': 'Editar Preço'})
|
|
|
|
# USER VIEWS
|
|
def catalog(request):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user and not is_admin:
|
|
return redirect('index')
|
|
|
|
projects = GameProject.objects.filter(is_active=True).order_by('-created_at')
|
|
return render(request, 'core/catalog.html', {'projects': projects, 'title': 'Catálogo de Jogos'})
|
|
|
|
def purchase_game(request, pk):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user:
|
|
return redirect('index')
|
|
|
|
game = get_object_or_404(GameProject, pk=pk)
|
|
options = RentalOption.objects.all()
|
|
|
|
if request.method == 'POST':
|
|
confirm_code = request.POST.get('confirm_code')
|
|
purchase_id = request.POST.get('purchase_id')
|
|
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
|
|
|
if confirm_code == purchase.confirmation_code and purchase.is_paid:
|
|
purchase.is_confirmed = True
|
|
purchase.save()
|
|
messages.success(request, f'Pagamento confirmado! Aproveite o jogo.')
|
|
return redirect('play_game', pk=game.pk)
|
|
else:
|
|
messages.error(request, 'Código inválido ou pagamento não identificado.')
|
|
|
|
return render(request, 'core/purchase.html', {
|
|
'game': game,
|
|
'options': options,
|
|
'title': f'Alugar {game.title}'
|
|
})
|
|
|
|
def generate_purchase(request, game_pk, option_pk):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user:
|
|
return JsonResponse({'success': False, 'error': 'Unauthorized'})
|
|
|
|
game = get_object_or_404(GameProject, pk=game_pk)
|
|
option = get_object_or_404(RentalOption, pk=option_pk)
|
|
|
|
expires = timezone.now() + timedelta(days=option.duration_days)
|
|
purchase = UserPurchase.objects.create(
|
|
user_session=user,
|
|
game=game,
|
|
rental_option=option,
|
|
confirmation_code="PENDING",
|
|
expires_at=expires,
|
|
is_paid=False,
|
|
is_confirmed=False
|
|
)
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'purchase_id': purchase.id
|
|
})
|
|
|
|
@csrf_exempt
|
|
def simulate_payment(request, purchase_id):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user:
|
|
return JsonResponse({'success': False, 'error': 'Unauthorized'})
|
|
|
|
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
|
real_code = ''.join(random.choices(string.digits, k=6))
|
|
purchase.confirmation_code = real_code
|
|
purchase.is_paid = True
|
|
purchase.save()
|
|
|
|
return JsonResponse({
|
|
'success': True,
|
|
'message': 'IDENTIFICAÇÃO DE PAGAMENTO REAL: Pagamento identificado e validado.'
|
|
})
|
|
|
|
def verify_payment_status(request, purchase_id):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user:
|
|
return JsonResponse({'success': False, 'error': 'Unauthorized'})
|
|
|
|
purchase = get_object_or_404(UserPurchase, pk=purchase_id, user_session=user)
|
|
|
|
if purchase.is_paid and purchase.confirmation_code != "PENDING":
|
|
return JsonResponse({
|
|
'success': True,
|
|
'paid': True,
|
|
'confirmation_code': purchase.confirmation_code
|
|
})
|
|
else:
|
|
return JsonResponse({
|
|
'success': True,
|
|
'paid': False
|
|
})
|
|
|
|
def play_game(request, pk):
|
|
is_admin, user = get_auth_status(request)
|
|
if not user and not is_admin:
|
|
return redirect('index')
|
|
|
|
game = get_object_or_404(GameProject, pk=pk)
|
|
|
|
if not is_admin:
|
|
purchase = UserPurchase.objects.filter(
|
|
user_session=user,
|
|
game=game,
|
|
expires_at__gt=timezone.now(),
|
|
is_confirmed=True
|
|
).first()
|
|
|
|
if not purchase:
|
|
messages.error(request, 'Você não tem tempo de jogo para este título.')
|
|
return redirect('purchase_game', pk=game.pk)
|
|
|
|
return render(request, 'core/play.html', {'game': game, 'title': f'Jogando {game.title}'}) |