Merge branch 'develop' into feature/websocket-init
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Game;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Requests\StoreGameRequest;
|
||||
|
||||
class GameController extends Controller
|
||||
{
|
||||
@@ -56,9 +57,10 @@ class GameController extends Controller
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(Request $request)
|
||||
public function store(StoreGameRequest $request)
|
||||
{
|
||||
//
|
||||
$game = Game::create($request->validated());
|
||||
return response()->json($game, 201);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,7 +68,7 @@ class GameController extends Controller
|
||||
*/
|
||||
public function show(Game $game)
|
||||
{
|
||||
//
|
||||
return $game;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,7 +76,8 @@ class GameController extends Controller
|
||||
*/
|
||||
public function update(Request $request, Game $game)
|
||||
{
|
||||
//
|
||||
$game->update($request->validated());
|
||||
return response()->json($game);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreGameRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,24 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use App\Http\Requests\StoreGameRequest;
|
||||
|
||||
|
||||
class Game extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'player1_id',
|
||||
'player2_id',
|
||||
'winner_id',
|
||||
'type',
|
||||
'status',
|
||||
'began_at',
|
||||
'ended_at',
|
||||
'total_time',
|
||||
'player1_moves',
|
||||
'player2_moves',
|
||||
];
|
||||
|
||||
public function winner()
|
||||
{
|
||||
return $this->belongsTo(User::class, "winner_user_id");
|
||||
|
||||
Generated
+1
-1
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"axios": "^1.13.2",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
"axios": "^1.11.0",
|
||||
"axios": "^1.13.2",
|
||||
"concurrently": "^9.0.1",
|
||||
"laravel-vite-plugin": "^2.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useApiStore } from './api'
|
||||
|
||||
export const useGameStore = defineStore('game', () => {
|
||||
const apiStore = useApiStore()
|
||||
|
||||
// Game state
|
||||
const game = ref({
|
||||
players: {
|
||||
human: { hand: [], score: 0, wonCards: [] },
|
||||
bot: { hand: [], score: 0, wonCards: [] },
|
||||
},
|
||||
deck: [],
|
||||
trump: null,
|
||||
currentTrick: [],
|
||||
lastWinner: null,
|
||||
gameOver: false,
|
||||
winner: null,
|
||||
})
|
||||
|
||||
const gameStarted = ref(false)
|
||||
const isHumanTurn = ref(true)
|
||||
const selectedCard = ref(null)
|
||||
const beganAt = ref(null)
|
||||
const endedAt = ref(null)
|
||||
const moves = ref([])
|
||||
|
||||
// Computed
|
||||
const humanScore = computed(() => {
|
||||
return game.value.players.human.wonCards.reduce((sum, card) => sum + card.value, 0)
|
||||
})
|
||||
|
||||
const botScore = computed(() => {
|
||||
return game.value.players.bot.wonCards.reduce((sum, card) => sum + card.value, 0)
|
||||
})
|
||||
|
||||
// Initialize deck
|
||||
const initializeDeck = () => {
|
||||
const suits = ['♥', '♦', '♣', '♠']
|
||||
const ranks = ['2', '3', '4', '5', '6', '7', 'J', 'Q', 'K', 'A']
|
||||
const values = { '2': 0, '3': 0, '4': 0, '5': 0, '6': 0, '7': 0, 'J': 2, 'Q': 3, 'K': 4, 'A': 11 }
|
||||
|
||||
const deck = []
|
||||
for (const suit of suits) {
|
||||
for (const rank of ranks) {
|
||||
deck.push({ rank, suit, value: values[rank] })
|
||||
}
|
||||
}
|
||||
|
||||
return deck.sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
// Start new game
|
||||
const startGame = () => {
|
||||
game.value.deck = initializeDeck()
|
||||
game.value.trump = game.value.deck[game.value.deck.length - 1]
|
||||
game.value.players.human.hand = game.value.deck.splice(0, 3)
|
||||
game.value.players.bot.hand = game.value.deck.splice(0, 3)
|
||||
game.value.currentTrick = []
|
||||
game.value.gameOver = false
|
||||
game.value.winner = null
|
||||
game.value.players.human.score = 0
|
||||
game.value.players.bot.score = 0
|
||||
game.value.players.human.wonCards = []
|
||||
game.value.players.bot.wonCards = []
|
||||
gameStarted.value = true
|
||||
isHumanTurn.value = true
|
||||
beganAt.value = new Date()
|
||||
endedAt.value = null
|
||||
moves.value = []
|
||||
}
|
||||
|
||||
// Play card
|
||||
const playCard = async (cardIndex) => {
|
||||
if (!isHumanTurn.value || game.value.gameOver) return
|
||||
|
||||
const card = game.value.players.human.hand[cardIndex]
|
||||
game.value.currentTrick.push({ player: 'human', card })
|
||||
game.value.players.human.hand.splice(cardIndex, 1)
|
||||
selectedCard.value = null
|
||||
|
||||
// Track move
|
||||
moves.value.push({
|
||||
player: 'human',
|
||||
card,
|
||||
timestamp: new Date(),
|
||||
})
|
||||
|
||||
isHumanTurn.value = false
|
||||
|
||||
// Bot plays
|
||||
setTimeout(() => {
|
||||
botPlay()
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
// Bot play logic
|
||||
const botPlay = () => {
|
||||
const botHand = game.value.players.bot.hand
|
||||
let cardToPlay
|
||||
|
||||
if (game.value.currentTrick.length === 0) {
|
||||
cardToPlay = botHand[0]
|
||||
} else {
|
||||
const leadCard = game.value.currentTrick[0].card
|
||||
const canWin = botHand.some((c) => canBeat(c, leadCard))
|
||||
|
||||
if (canWin) {
|
||||
cardToPlay = botHand.filter((c) => canBeat(c, leadCard)).sort((a, b) => a.value - b.value)[0]
|
||||
} else {
|
||||
cardToPlay = botHand.sort((a, b) => a.value - b.value)[0]
|
||||
}
|
||||
}
|
||||
|
||||
game.value.currentTrick.push({ player: 'bot', card: cardToPlay })
|
||||
game.value.players.bot.hand.splice(game.value.players.bot.hand.indexOf(cardToPlay), 1)
|
||||
|
||||
// Track move
|
||||
moves.value.push({
|
||||
player: 'bot',
|
||||
card: cardToPlay,
|
||||
timestamp: new Date(),
|
||||
})
|
||||
|
||||
// Resolve trick
|
||||
setTimeout(() => {
|
||||
resolveTrick()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// Check if card can beat
|
||||
const canBeat = (card, leadCard) => {
|
||||
if (card.suit === leadCard.suit) {
|
||||
return rankValue(card.rank) > rankValue(leadCard.rank)
|
||||
}
|
||||
return card.suit === game.value.trump.suit
|
||||
}
|
||||
|
||||
// Get rank value for comparison
|
||||
const rankValue = (rank) => {
|
||||
const values = { '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, 'J': 11, 'Q': 12, 'K': 13, 'A': 14 }
|
||||
return values[rank] || 0
|
||||
}
|
||||
|
||||
// Resolve trick
|
||||
const resolveTrick = () => {
|
||||
const trick = game.value.currentTrick
|
||||
const card1 = trick[0].card
|
||||
const card2 = trick[1].card
|
||||
|
||||
let winner
|
||||
if (card2.suit === card1.suit) {
|
||||
winner = rankValue(card2.rank) > rankValue(card1.rank) ? 'bot' : 'human'
|
||||
} else if (card2.suit === game.value.trump.suit) {
|
||||
winner = 'bot'
|
||||
} else {
|
||||
winner = 'human'
|
||||
}
|
||||
|
||||
game.value.lastWinner = winner
|
||||
game.value.players[winner].wonCards.push(card1, card2)
|
||||
|
||||
// Refill hands
|
||||
refillHands(winner)
|
||||
|
||||
game.value.currentTrick = []
|
||||
|
||||
// Check if game is over
|
||||
if (game.value.players.human.hand.length === 0 && game.value.deck.length === 0) {
|
||||
endGame()
|
||||
} else {
|
||||
isHumanTurn.value = winner === 'human'
|
||||
if (winner === 'bot') {
|
||||
setTimeout(() => botPlay(), 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refill hands
|
||||
const refillHands = (winner) => {
|
||||
const order = winner === 'human' ? ['human', 'bot'] : ['bot', 'human']
|
||||
|
||||
for (const player of order) {
|
||||
while (game.value.players[player].hand.length < 3 && game.value.deck.length > 0) {
|
||||
game.value.players[player].hand.push(game.value.deck.pop())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// End game
|
||||
const endGame = () => {
|
||||
game.value.gameOver = true
|
||||
game.value.winner = humanScore.value > botScore.value ? 'human' : 'bot'
|
||||
endedAt.value = new Date()
|
||||
}
|
||||
|
||||
// Save game to API
|
||||
const saveGame = async () => {
|
||||
const gameData = {
|
||||
type: 'S', // Standalone game
|
||||
status: 'E', // Ended
|
||||
player1_moves: moves.value,
|
||||
began_at: beganAt.value,
|
||||
ended_at: endedAt.value,
|
||||
total_time: Math.ceil((endedAt.value - beganAt.value) / 1000),
|
||||
}
|
||||
|
||||
return toast.promise(apiStore.postGame(gameData), {
|
||||
loading: 'Sending data to API...',
|
||||
success: () => {
|
||||
return '[API] Game saved successfully'
|
||||
},
|
||||
error: (data) => `[API] Error saving game - ${data?.response?.data?.message}`,
|
||||
})
|
||||
}
|
||||
|
||||
// Reset game
|
||||
const reset = () => {
|
||||
gameStarted.value = false
|
||||
selectedCard.value = null
|
||||
beganAt.value = null
|
||||
endedAt.value = null
|
||||
moves.value = []
|
||||
game.value = {
|
||||
players: {
|
||||
human: { hand: [], score: 0, wonCards: [] },
|
||||
bot: { hand: [], score: 0, wonCards: [] },
|
||||
},
|
||||
deck: [],
|
||||
trump: null,
|
||||
currentTrick: [],
|
||||
lastWinner: null,
|
||||
gameOver: false,
|
||||
winner: null,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
game,
|
||||
gameStarted,
|
||||
isHumanTurn,
|
||||
selectedCard,
|
||||
beganAt,
|
||||
endedAt,
|
||||
moves,
|
||||
humanScore,
|
||||
botScore,
|
||||
startGame,
|
||||
playCard,
|
||||
saveGame,
|
||||
reset,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user