Added endpoits to create, join and update the game state, playability should work now
This commit is contained in:
@@ -40,23 +40,24 @@ class AuthController extends Controller
|
||||
'message' => 'Logged out successfully',
|
||||
]);
|
||||
}
|
||||
|
||||
public function register(RegisterRequest $request)
|
||||
{
|
||||
if (User::where('email', $request->email)->exists()) {
|
||||
return response()->json(['message' => 'Email already registered'], 409);
|
||||
}
|
||||
|
||||
|
||||
$user = User::create([
|
||||
'email' => $request->email,
|
||||
'nickname' => $request->nickname,
|
||||
'name' => $request->name,
|
||||
'password' => bcrypt($request->password),
|
||||
'photo_avatar_filename' => $request->photo,
|
||||
'type' => 'P',
|
||||
'blocked' => false,
|
||||
'coins_balance' => 10,
|
||||
]);
|
||||
|
||||
$user->coins_balance = 10;
|
||||
$user->save();
|
||||
|
||||
$token = $user->createToken('auth-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
|
||||
@@ -5,15 +5,13 @@ namespace App\Http\Controllers;
|
||||
use App\Models\Game;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\StoreGameRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class GameController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Game::query()->with(['winner']);
|
||||
$query = Game::query()->with(['winner', 'player1', 'player2']);
|
||||
|
||||
if ($request->has('type') && in_array($request->type, ['3', '9'])) {
|
||||
$query->where('type', $request->type);
|
||||
@@ -23,137 +21,114 @@ class GameController extends Controller
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
// Sorting
|
||||
$sortField = $request->input('sort_by', 'began_at');
|
||||
$sortDirection = $request->input('sort_direction', 'desc');
|
||||
$query->orderBy('began_at', 'desc');
|
||||
|
||||
$allowedSortFields = [
|
||||
'began_at',
|
||||
'ended_at',
|
||||
'total_time',
|
||||
'type',
|
||||
'status'
|
||||
];
|
||||
|
||||
if (in_array($sortField, $allowedSortFields)) {
|
||||
$query->orderBy($sortField, $sortDirection === 'asc' ? 'asc' : 'desc');
|
||||
}
|
||||
|
||||
// Pagination
|
||||
$perPage = $request->input('per_page', 15);
|
||||
$games = $query->paginate($perPage);
|
||||
|
||||
return response()->json([
|
||||
'data' => $games->items(),
|
||||
'meta' => [
|
||||
'current_page' => $games->currentPage(),
|
||||
'last_page' => $games->lastPage(),
|
||||
'per_page' => $games->perPage(),
|
||||
'total' => $games->total()
|
||||
]
|
||||
]);
|
||||
return response()->json($query->paginate(15));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreGameRequest $request)
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validated();
|
||||
$user = $request->user();
|
||||
$validated = $request->validate([
|
||||
'type' => 'required|in:3,9',
|
||||
]);
|
||||
|
||||
$type = $validated['variant'] === 'bisca-3' ? '3' : '9';
|
||||
$initialState = $this->initializeGameState($type);
|
||||
$user = Auth::user();
|
||||
|
||||
$activeGame = Game::where(function ($query) use ($user) {
|
||||
$query->where('player1_user_id', $user->id)
|
||||
->orWhere('player2_user_id', $user->id);
|
||||
})
|
||||
->whereIn('status', ['Pending', 'Playing'])
|
||||
->exists();
|
||||
|
||||
if ($activeGame) {
|
||||
return response()->json(['message' => 'You already have an active game.'], 400);
|
||||
}
|
||||
|
||||
$game = Game::create([
|
||||
'type' => $type,
|
||||
'status' => 'in_progress',
|
||||
'type' => $validated['type'],
|
||||
'status' => 'Pending',
|
||||
'player1_user_id' => $user->id,
|
||||
'player2_user_id' => $validated['mode'] === 'multiplayer'
|
||||
? $validated['opponent_id']
|
||||
: 1, // REPLACE 1 with your actual Bot User ID
|
||||
'player2_user_id' => $user->id,
|
||||
'winner_user_id' => $user->id,
|
||||
'loser_user_id' => $user->id,
|
||||
'began_at' => now(),
|
||||
'custom' => json_encode($initialState),
|
||||
'player1_points' => 0,
|
||||
'player2_points' => 0,
|
||||
'custom' => null
|
||||
]);
|
||||
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Game created successfully',
|
||||
'game_id' => $game->id,
|
||||
'game_state' => $initialState,
|
||||
], 201);
|
||||
'message' => 'Multiplayer game room created',
|
||||
'game' => $game
|
||||
], 201);
|
||||
}
|
||||
|
||||
private function initializeGameState($type)
|
||||
public function join(Game $game)
|
||||
{
|
||||
$handSize = $type === 'S' ? 3 : 9;
|
||||
|
||||
$suits = ['H', 'D', 'C', 'S'];
|
||||
$ranks = ['2', '3', '4', '5', '6', '7', 'J', 'Q', 'K', 'A'];
|
||||
|
||||
$values = [
|
||||
'2' => 0, '3' => 0, '4' => 0, '5' => 0, '6' => 0,
|
||||
'7' => 10, 'J' => 3, 'Q' => 2, 'K' => 4, 'A' => 11
|
||||
];
|
||||
|
||||
$deck = [];
|
||||
foreach ($suits as $suit) {
|
||||
foreach ($ranks as $rank) {
|
||||
$deck[] = ['suit' => $suit, 'rank' => $rank, 'value' => $values[$rank]];
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle and deal
|
||||
shuffle($deck);
|
||||
$player1Hand = array_splice($deck, 0, $handSize);
|
||||
$player2Hand = array_splice($deck, 0, $handSize);
|
||||
|
||||
// Trump is the suit of the last card in the remaining stock
|
||||
$trumpCard = null;
|
||||
if (count($deck) > 0) {
|
||||
$trumpCard = $deck[count($deck) - 1]; // Bottom card
|
||||
}
|
||||
$user = Auth::user();
|
||||
|
||||
return [
|
||||
'player1_hand' => $player1Hand,
|
||||
'player2_hand' => $player2Hand,
|
||||
'stock' => $deck,
|
||||
'trump' => $trumpCard ? $trumpCard['suit'] : null,
|
||||
'current_trick' => [],
|
||||
'current_turn' => 'player1',
|
||||
'score' => [
|
||||
'player1' => 0,
|
||||
'player2' => 0
|
||||
],
|
||||
'captured' => [
|
||||
'player1' => [],
|
||||
'player2' => []
|
||||
]
|
||||
];
|
||||
if ($game->player1_user_id == $user->id) {
|
||||
return response()->json([
|
||||
'message' => 'Waiting for opponent...',
|
||||
'game' => $game
|
||||
], 200);
|
||||
}
|
||||
|
||||
if ($game->status !== 'Pending') {
|
||||
return response()->json(['message' => 'Game is full or already started'], 400);
|
||||
}
|
||||
|
||||
$activeGame = Game::where(function ($query) use ($user) {
|
||||
$query->where('player1_user_id', $user->id)
|
||||
->orWhere('player2_user_id', $user->id);
|
||||
})
|
||||
->whereIn('status', ['Pending', 'Playing'])
|
||||
->exists();
|
||||
|
||||
if ($activeGame) {
|
||||
return response()->json(['message' => 'You are already in another active game.'], 400);
|
||||
}
|
||||
|
||||
$game->update([
|
||||
'status' => 'Playing',
|
||||
'player2_user_id' => $user->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Joined successfully! Game starts now.',
|
||||
'game' => $game
|
||||
], 200);
|
||||
}
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
|
||||
public function show(Game $game)
|
||||
{
|
||||
return $game;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(Request $request, Game $game)
|
||||
{
|
||||
$game->update($request->validated());
|
||||
$game->load(['player1', 'player2', 'winner']);
|
||||
return response()->json($game);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(Game $game)
|
||||
public function update(Request $request, Game $game)
|
||||
{
|
||||
//
|
||||
if ($game->status == 'Ended') {
|
||||
return response()->json(['message' => 'Game already ended'], 400);
|
||||
}
|
||||
|
||||
$data = $request->validate([
|
||||
'status' => 'in:Playing,Ended,Interrupted',
|
||||
'winner_user_id' => 'exists:users,id',
|
||||
'loser_user_id' => 'exists:users,id',
|
||||
'player1_points' => 'integer',
|
||||
'player2_points' => 'integer',
|
||||
'is_draw' => 'boolean',
|
||||
'total_time' => 'numeric'
|
||||
]);
|
||||
|
||||
if (isset($data['status']) && $data['status'] === 'Ended') {
|
||||
$data['ended_at'] = now();
|
||||
}
|
||||
|
||||
$game->update($data);
|
||||
|
||||
return response()->json($game);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,149 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Game;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\StoreGameRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* GET /api/users
|
||||
* Lista todos os utilizadores (Apenas para Admins)
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->user()->type !== 'A') {
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$query = User::query();
|
||||
|
||||
// Filtros úteis para o Backoffice
|
||||
if ($request->has('type')) {
|
||||
$query->where('type', $request->type);
|
||||
}
|
||||
if ($request->has('search')) {
|
||||
$query->where('name', 'like', '%' . $request->search . '%')
|
||||
->orWhere('email', 'like', '%' . $request->search . '%')
|
||||
->orWhere('nickname', 'like', '%' . $request->search . '%');
|
||||
}
|
||||
|
||||
return response()->json($query->paginate(15));
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/me
|
||||
* Retorna o perfil do utilizador autenticado atual.
|
||||
* Usado pelo VueJS para saber quem está logado.
|
||||
*/
|
||||
public function me(Request $request)
|
||||
{
|
||||
return response()->json($request->user());
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/{user}
|
||||
* Mostra um perfil específico.
|
||||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
$currentUser = Auth::user();
|
||||
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
|
||||
return response()->json([
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'nickname' => $user->nickname,
|
||||
'photo_avatar_filename' => $user->photo_avatar_filename,
|
||||
'type' => $user->type
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT/PATCH /api/users/{user}
|
||||
* Atualizar perfil (Nome, Nickname, Password, Foto)
|
||||
*/
|
||||
public function update(Request $request, User $user)
|
||||
{
|
||||
$currentUser = Auth::user();
|
||||
|
||||
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'sometimes|string|max:255',
|
||||
'nickname' => ['sometimes', 'string', 'max:20', Rule::unique('users')->ignore($user->id)],
|
||||
'email' => ['sometimes', 'email', Rule::unique('users')->ignore($user->id)],
|
||||
'password' => 'sometimes|string|min:3',
|
||||
'blocked' => 'sometimes|boolean',
|
||||
]);
|
||||
|
||||
if (isset($validated['blocked']) && $currentUser->type !== 'A') {
|
||||
return response()->json(['message' => 'Only admins can block users'], 403);
|
||||
}
|
||||
|
||||
// Lógica de Upload de Foto (Exemplo Básico)
|
||||
// if ($request->hasFile('photo_avatar')) {
|
||||
// $path = $request->file('photo_avatar')->store('avatars', 'public');
|
||||
// $validated['photo_avatar_filename'] = basename($path);
|
||||
// }
|
||||
|
||||
$user->update($validated);
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/users/{user}
|
||||
* Apagar conta (Soft Delete)
|
||||
*/
|
||||
public function destroy(User $user)
|
||||
{
|
||||
$currentUser = Auth::user();
|
||||
|
||||
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
if ($currentUser->id === $user->id && $currentUser->type === 'A') {
|
||||
if ($user->type === 'A') {
|
||||
return response()->json(['message' => 'Admins cannot delete their own account'], 403);
|
||||
}
|
||||
}
|
||||
|
||||
$user->delete();
|
||||
|
||||
return response()->json(['message' => 'User deleted successfully']);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/users/{user}/matches
|
||||
* Histórico de Jogos Multiplayer do Utilizador
|
||||
*/
|
||||
public function getMatches(Request $request, User $user)
|
||||
{
|
||||
$currentUser = Auth::user();
|
||||
|
||||
if ($currentUser->id !== $user->id && $currentUser->type !== 'A') {
|
||||
return response()->json(['message' => 'Unauthorized'], 403);
|
||||
}
|
||||
|
||||
$matches = Game::query()
|
||||
->where(function($q) use ($user) {
|
||||
$q->where('player1_user_id', $user->id)
|
||||
->orWhere('player2_user_id', $user->id);
|
||||
})
|
||||
->with(['winner', 'player1', 'player2'])
|
||||
->orderBy('began_at', 'desc')
|
||||
->paginate(10);
|
||||
|
||||
return response()->json($matches);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class StoreGameRequest extends FormRequest
|
||||
{
|
||||
@@ -11,7 +12,8 @@ class StoreGameRequest extends FormRequest
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
// Só utilizadores autenticados podem criar jogos
|
||||
return Auth::check();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,11 +24,19 @@ class StoreGameRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'variant' => 'required|in:bisca-3,bisca-9',
|
||||
'mode' => 'required|in:single-player,multiplayer',
|
||||
'opponent_id' => $this->mode === 'multiplayer'
|
||||
? 'required|exists:users,id'
|
||||
: 'nullable',
|
||||
// Agora validamos diretamente '3' ou '9' para bater certo com a BD
|
||||
'type' => 'required|in:3,9',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mensagens personalizadas (Opcional)
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'type.required' => 'You must choose a game type (Bisca de 3 or 9).',
|
||||
'type.in' => 'Invalid game type. Choose 3 or 9.',
|
||||
];
|
||||
}
|
||||
}
|
||||
+24
-5
@@ -4,25 +4,44 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Http\Requests\StoreGameRequest;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
|
||||
class Game extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'player1_id',
|
||||
'player2_id',
|
||||
'winner_id',
|
||||
'type',
|
||||
'status',
|
||||
'player1_user_id',
|
||||
'player2_user_id',
|
||||
'winner_user_id',
|
||||
'loser_user_id',
|
||||
'began_at',
|
||||
'ended_at',
|
||||
'total_time',
|
||||
'player1_moves',
|
||||
'player2_moves',
|
||||
'player1_points',
|
||||
'player2_points',
|
||||
'custom',
|
||||
'is_draw',
|
||||
'match_id'
|
||||
];
|
||||
|
||||
public function winner()
|
||||
{
|
||||
return $this->belongsTo(User::class, "winner_user_id");
|
||||
}
|
||||
|
||||
public function player1()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'player1_user_id');
|
||||
}
|
||||
|
||||
public function player2()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'player2_user_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
@@ -11,7 +12,7 @@ use Laravel\Sanctum\HasApiTokens;
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable, HasApiTokens;
|
||||
use HasFactory, Notifiable, HasApiTokens, SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
@@ -24,6 +25,10 @@ class User extends Authenticatable
|
||||
'password',
|
||||
'nickname',
|
||||
'photo_avatar_filename',
|
||||
'type',
|
||||
'blocked',
|
||||
'coins_balance',
|
||||
'custom',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -46,6 +51,7 @@ class User extends Authenticatable
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'blocked' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@ Route::post('/register', [AuthController::class, 'register']);
|
||||
# Game Routes - Protected
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::apiResource('games', GameController::class);
|
||||
Route::post('games', [GameController::class, 'store']);
|
||||
Route::post('/games/{game}/resign', [GameController::class, 'resign']);
|
||||
Route::post('/games/{game}/play-card', [GameController::class, 'playCard']);
|
||||
Route::get('/users/{user}/matches', [UserController::class, 'getMatches']);
|
||||
Route::post('games/{game}/join', [GameController::class, 'join']);
|
||||
});
|
||||
Reference in New Issue
Block a user