feat: add GymSession model and TrainingService
Implements gym training logic with daily session tracking: - GymSession model manages training sessions per stat per day - TrainingService handles training validation, energy consumption, and stat gains - Lazy daily reset: session count resets when reset_date is before today - Validates: sufficient energy, daily cap (5 sessions per stat), valid stats Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
310dfd4129
commit
8dbcdfd70c
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $character_id
|
||||
* @property string $stat
|
||||
* @property int $sessions_today
|
||||
* @property \Illuminate\Support\Carbon $reset_date
|
||||
*/
|
||||
class GymSession extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'character_id',
|
||||
'stat',
|
||||
'sessions_today',
|
||||
'reset_date',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'reset_date' => 'date',
|
||||
];
|
||||
|
||||
public function character(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Character::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Game;
|
||||
|
||||
use App\Models\Character;
|
||||
use App\Models\GymSession;
|
||||
|
||||
class TrainingService
|
||||
{
|
||||
private const VALID_STATS = ['strength', 'defense', 'speed', 'dexterity'];
|
||||
|
||||
public function train(Character $character, string $stat): array
|
||||
{
|
||||
if (!in_array($stat, self::VALID_STATS, true)) {
|
||||
return ['success' => false, 'message' => 'Invalid stat.', 'stat_value' => 0];
|
||||
}
|
||||
|
||||
$energyCost = config('game.gym.energy_cost', 20);
|
||||
$dailyCap = config('game.gym.daily_cap', 5);
|
||||
$statGain = config('game.gym.stat_gain', 1);
|
||||
|
||||
if (!$character->hasEnergy($energyCost)) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Not enough energy ({$energyCost} required).",
|
||||
'stat_value' => $character->$stat,
|
||||
];
|
||||
}
|
||||
|
||||
$sessionsToday = $this->getSessionsToday($character, $stat);
|
||||
|
||||
if ($sessionsToday >= $dailyCap) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => "Daily training cap reached for {$stat} ({$dailyCap} sessions).",
|
||||
'stat_value' => $character->$stat,
|
||||
];
|
||||
}
|
||||
|
||||
// Deduct energy
|
||||
$character->consumeEnergy($energyCost);
|
||||
|
||||
// Increment stat
|
||||
$character->$stat += $statGain;
|
||||
$character->save();
|
||||
|
||||
// Upsert gym_sessions row
|
||||
$session = GymSession::firstOrNew([
|
||||
'character_id' => $character->id,
|
||||
'stat' => $stat,
|
||||
]);
|
||||
|
||||
if (!$session->reset_date || $session->reset_date->lt(today())) {
|
||||
$session->sessions_today = 0;
|
||||
$session->reset_date = today();
|
||||
}
|
||||
|
||||
$session->sessions_today += 1;
|
||||
$session->save();
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => ucfirst($stat) . " increased to {$character->$stat}!",
|
||||
'stat_value' => $character->$stat,
|
||||
];
|
||||
}
|
||||
|
||||
public function getSessionsToday(Character $character, string $stat): int
|
||||
{
|
||||
$session = GymSession::where('character_id', $character->id)
|
||||
->where('stat', $stat)
|
||||
->first();
|
||||
|
||||
if (!$session || $session->reset_date->lt(today())) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $session->sessions_today;
|
||||
}
|
||||
|
||||
public function getSessionsRemaining(Character $character, string $stat): int
|
||||
{
|
||||
$cap = config('game.gym.daily_cap', 5);
|
||||
return max(0, $cap - $this->getSessionsToday($character, $stat));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user