Created
May 17, 2025 01:32
-
-
Save bufordeeds/11eda11ea1865779c36303d0f4ad0e4d to your computer and use it in GitHub Desktop.
Skull Digital
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
const SkullGame = () => { | |
// Game state | |
const [gamePhase, setGamePhase] = useState('placement'); // placement, bidding, flipping, result | |
const [currentPlayer, setCurrentPlayer] = useState(0); | |
const [players, setPlayers] = useState([ | |
{ id: 0, name: 'You', cards: 4, stack: [], points: 0, isHuman: true }, | |
{ id: 1, name: 'Alice', cards: 4, stack: [], points: 0, isHuman: false }, | |
{ id: 2, name: 'Bob', cards: 4, stack: [], points: 0, isHuman: false }, | |
{ id: 3, name: 'Charlie', cards: 4, stack: [], points: 0, isHuman: false } | |
]); | |
const [currentBid, setCurrentBid] = useState(0); | |
const [highestBidder, setHighestBidder] = useState(null); | |
const [selectedCard, setSelectedCard] = useState(null); | |
const [cardsFlipped, setCardsFlipped] = useState(0); | |
const [message, setMessage] = useState("Place your first card"); | |
// Player's hand (3 roses, 1 skull) | |
const [playerHand, setPlayerHand] = useState([ | |
{ id: 1, type: 'rose', selected: false }, | |
{ id: 2, type: 'rose', selected: false }, | |
{ id: 3, type: 'rose', selected: false }, | |
{ id: 4, type: 'skull', selected: false } | |
]); | |
// Functions | |
const selectCard = (cardId) => { | |
if (gamePhase !== 'placement' || currentPlayer !== 0) return; | |
const newHand = playerHand.map(card => ({ | |
...card, | |
selected: card.id === cardId | |
})); | |
setPlayerHand(newHand); | |
setSelectedCard(cardId); | |
}; | |
const placeCard = () => { | |
if (!selectedCard || gamePhase !== 'placement' || currentPlayer !== 0) return; | |
// Find the selected card | |
const card = playerHand.find(card => card.id === selectedCard); | |
// Update player's stack | |
const newPlayers = [...players]; | |
newPlayers[0].stack.push({ type: card.type, faceUp: false }); | |
// Remove card from hand | |
const newHand = playerHand.filter(card => card.id !== selectedCard); | |
setPlayerHand(newHand); | |
setSelectedCard(null); | |
// Move to next player or phase | |
if (newPlayers.every(player => player.stack.length > 0)) { | |
setGamePhase('bidding'); | |
setMessage("Bidding phase: Make your bid"); | |
} else { | |
setCurrentPlayer((currentPlayer + 1) % players.length); | |
setMessage(`${players[(currentPlayer + 1) % players.length].name}'s turn to place a card`); | |
} | |
setPlayers(newPlayers); | |
}; | |
const makeBid = (bid) => { | |
if (gamePhase !== 'bidding' || currentPlayer !== 0) return; | |
setCurrentBid(bid); | |
setHighestBidder(0); | |
setCurrentPlayer(1); | |
setMessage(`You bid ${bid}. ${players[1].name}'s turn to bid`); | |
}; | |
const pass = () => { | |
if (gamePhase !== 'bidding' || currentPlayer !== 0) return; | |
setMessage(`You passed. ${players[1].name}'s turn to bid`); | |
setCurrentPlayer(1); | |
}; | |
const startFlipping = () => { | |
if (gamePhase !== 'bidding' || highestBidder !== 0) return; | |
setGamePhase('flipping'); | |
setMessage("Start flipping cards from your stack first"); | |
}; | |
// Simulated AI moves for demo purposes | |
React.useEffect(() => { | |
if (!players[currentPlayer]?.isHuman && currentPlayer !== 0) { | |
const timer = setTimeout(() => { | |
// Simple AI behavior | |
if (gamePhase === 'placement') { | |
// AI places a card | |
const newPlayers = [...players]; | |
// 20% chance of placing a skull | |
const cardType = Math.random() < 0.2 ? 'skull' : 'rose'; | |
newPlayers[currentPlayer].stack.push({ type: cardType, faceUp: false }); | |
setPlayers(newPlayers); | |
if (newPlayers.every(player => player.stack.length > 0)) { | |
setGamePhase('bidding'); | |
setMessage("Bidding phase: Make your bid"); | |
setCurrentPlayer(0); | |
} else { | |
setCurrentPlayer((currentPlayer + 1) % players.length); | |
setMessage(`${players[(currentPlayer + 1) % players.length].name}'s turn to place a card`); | |
} | |
} else if (gamePhase === 'bidding') { | |
// AI bids or passes | |
if (Math.random() < 0.6 && currentBid < 8) { | |
// AI makes a bid | |
const newBid = currentBid + 1; | |
setCurrentBid(newBid); | |
setHighestBidder(currentPlayer); | |
setMessage(`${players[currentPlayer].name} bids ${newBid}`); | |
} else { | |
// AI passes | |
setMessage(`${players[currentPlayer].name} passes`); | |
} | |
const nextPlayer = (currentPlayer + 1) % players.length; | |
setCurrentPlayer(nextPlayer); | |
// If we've gone all the way around, highest bidder starts flipping | |
if (nextPlayer === highestBidder) { | |
setGamePhase('flipping'); | |
if (highestBidder === 0) { | |
setMessage("Start flipping cards from your stack first"); | |
} else { | |
setMessage(`${players[highestBidder].name} starts flipping cards`); | |
// Simulate AI flipping for demo | |
setTimeout(() => { | |
setGamePhase('result'); | |
// 50/50 success/failure for demo | |
if (Math.random() < 0.5) { | |
setMessage(`${players[highestBidder].name} succeeded and gained a point!`); | |
const newPlayers = [...players]; | |
newPlayers[highestBidder].points += 1; | |
setPlayers(newPlayers); | |
} else { | |
setMessage(`${players[highestBidder].name} flipped a skull and lost a card!`); | |
const newPlayers = [...players]; | |
newPlayers[highestBidder].cards -= 1; | |
setPlayers(newPlayers); | |
} | |
// Reset for next round | |
setTimeout(() => { | |
resetGame(); | |
}, 3000); | |
}, 2000); | |
} | |
} | |
} | |
}, 1500); | |
return () => clearTimeout(timer); | |
} | |
}, [currentPlayer, gamePhase, players, currentBid, highestBidder]); | |
const resetGame = () => { | |
// Reset for next round - simplified for demo | |
setGamePhase('placement'); | |
setCurrentPlayer(0); | |
setCurrentBid(0); | |
setHighestBidder(null); | |
setCardsFlipped(0); | |
setMessage("Place your first card"); | |
// Reset stacks but keep points and remaining cards | |
const newPlayers = players.map(player => ({ | |
...player, | |
stack: [] | |
})); | |
setPlayers(newPlayers); | |
// Give player new cards | |
setPlayerHand([ | |
{ id: 1, type: 'rose', selected: false }, | |
{ id: 2, type: 'rose', selected: false }, | |
{ id: 3, type: 'rose', selected: false }, | |
{ id: 4, type: 'skull', selected: false } | |
]); | |
}; | |
return ( | |
<div className="flex flex-col items-center justify-between h-screen bg-gray-900 text-white p-4"> | |
{/* Game header */} | |
<div className="w-full text-center mb-2"> | |
<h1 className="text-2xl font-bold">Skull</h1> | |
<p className="text-sm text-gray-300">Round 1</p> | |
</div> | |
{/* Message area */} | |
<div className="bg-gray-800 rounded-lg p-3 w-full text-center mb-2"> | |
<p>{message}</p> | |
</div> | |
{/* AI Players */} | |
<div className="w-full flex justify-between mb-6"> | |
{players.slice(1).map(player => ( | |
<div key={player.id} className={`text-center p-2 ${currentPlayer === player.id ? 'bg-gray-700 rounded-lg' : ''}`}> | |
<p className="text-sm">{player.name}</p> | |
<div className="flex justify-center gap-1 my-1"> | |
{Array(player.points).fill(0).map((_, i) => ( | |
<div key={i} className="w-3 h-3 bg-yellow-500 rounded-full"></div> | |
))} | |
</div> | |
<div className="relative h-16 w-12 mx-auto"> | |
{player.stack.map((card, index) => ( | |
<div | |
key={index} | |
className="absolute h-16 w-12 border rounded-lg" | |
style={{ | |
backgroundColor: '#444', | |
transform: `translateY(${-index * 3}px)`, | |
zIndex: index | |
}} | |
></div> | |
))} | |
</div> | |
<p className="text-xs mt-1">Cards: {player.cards}</p> | |
</div> | |
))} | |
</div> | |
{/* Center play area */} | |
<div className="flex-grow w-full flex flex-col items-center justify-center"> | |
{gamePhase === 'bidding' && currentPlayer === 0 && ( | |
<div className="flex flex-col items-center gap-4"> | |
<p className="text-lg">Current bid: {currentBid}</p> | |
<div className="flex gap-2"> | |
{[currentBid + 1, currentBid + 2, currentBid + 3].map(bid => ( | |
<button | |
key={bid} | |
className="bg-blue-600 px-4 py-2 rounded-lg" | |
onClick={() => makeBid(bid)} | |
> | |
Bid {bid} | |
</button> | |
))} | |
</div> | |
<button | |
className="bg-red-600 px-6 py-2 rounded-lg mt-2" | |
onClick={pass} | |
> | |
Pass | |
</button> | |
</div> | |
)} | |
{gamePhase === 'flipping' && highestBidder === 0 && ( | |
<div className="text-center"> | |
<p className="mb-4">Flip {currentBid} cards without finding a skull</p> | |
<p className="text-lg">Cards flipped: {cardsFlipped} / {currentBid}</p> | |
<div className="mt-4"> | |
<button | |
className="bg-purple-600 px-6 py-2 rounded-lg" | |
onClick={() => { | |
// Simple demo - 30% chance of finding a skull | |
if (Math.random() < 0.3) { | |
setMessage("You flipped a skull and lost!"); | |
setGamePhase('result'); | |
const newPlayers = [...players]; | |
newPlayers[0].cards -= 1; | |
setPlayers(newPlayers); | |
setTimeout(resetGame, 3000); | |
} else { | |
const newFlipped = cardsFlipped + 1; | |
setCardsFlipped(newFlipped); | |
if (newFlipped >= currentBid) { | |
setMessage("Success! You gained a point!"); | |
setGamePhase('result'); | |
const newPlayers = [...players]; | |
newPlayers[0].points += 1; | |
setPlayers(newPlayers); | |
setTimeout(resetGame, 3000); | |
} | |
} | |
}} | |
> | |
Flip Next Card | |
</button> | |
</div> | |
</div> | |
)} | |
</div> | |
{/* Player's area */} | |
<div className="w-full"> | |
<div className="flex justify-center gap-1 mb-1"> | |
{Array(players[0].points).fill(0).map((_, i) => ( | |
<div key={i} className="w-3 h-3 bg-yellow-500 rounded-full"></div> | |
))} | |
</div> | |
<div className="relative h-20 mx-auto w-16"> | |
{players[0].stack.map((card, index) => ( | |
<div | |
key={index} | |
className="absolute h-20 w-16 border rounded-lg" | |
style={{ | |
backgroundColor: '#444', | |
transform: `translateY(${-index * 4}px)`, | |
zIndex: index | |
}} | |
></div> | |
))} | |
</div> | |
{/* Player's hand */} | |
{gamePhase === 'placement' && currentPlayer === 0 && ( | |
<> | |
<div className="flex justify-center gap-3 mt-4 mb-2"> | |
{playerHand.map(card => ( | |
<div | |
key={card.id} | |
className={`h-28 w-20 rounded-lg flex items-center justify-center cursor-pointer transition-transform ${ | |
card.selected ? 'border-4 border-yellow-400 transform -translate-y-2' : 'border border-gray-600' | |
}`} | |
style={{ backgroundColor: card.type === 'rose' ? '#722ed1' : '#d4380d' }} | |
onClick={() => selectCard(card.id)} | |
> | |
<div className="text-center"> | |
{card.type === 'rose' ? ( | |
<div className="text-2xl">🌹</div> | |
) : ( | |
<div className="text-2xl">💀</div> | |
)} | |
</div> | |
</div> | |
))} | |
</div> | |
<div className="flex justify-center mt-2"> | |
<button | |
className={`px-6 py-3 rounded-lg ${selectedCard ? 'bg-green-600' : 'bg-gray-600'}`} | |
disabled={!selectedCard} | |
onClick={placeCard} | |
> | |
Place Card | |
</button> | |
</div> | |
</> | |
)} | |
</div> | |
</div> | |
); | |
}; | |
export default SkullGame; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment