用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/RunnerQuan/SAFE-Agent --skill virtual-currency-system-prop-coins命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
基于 SOC 职业分类
| name | Virtual Currency System (Prop Coins) |
| description | Rules, patterns, and anti-cheating for virtual currency |
| when_to_use | Working with Prop Coins, wagers, transactions, or balance |
| version | 1.0.0 |
| languages | swift |
THIS IS NOT REAL MONEY
struct CurrencyConstants {
static let startingBalance = 1000
static let dailyReward = 100
static let minimumWager = 10
}
@MainActor
class CurrencyManager: ObservableObject {
static let shared = CurrencyManager()
@Published private(set) var balance: Int
@Published private(set) var transactions: [Transaction] = []
private var lockedFunds: [UUID: Int] = [:]
private let queue = DispatchQueue(label: "currency.serial")
private init() {
self.balance = UserDefaults.standard.integer(forKey: "balance")
if balance == 0 {
balance = CurrencyConstants.startingBalance
saveBalance()
}
}
// MARK: - Wager Management
func canWager(_ amount: Int) -> Bool {
return amount >= CurrencyConstants.minimumWager && amount <= availableBalance
}
var availableBalance: {
locked lockedFunds.values.reduce(, )
balance locked
}
( : , : ) -> {
canWager(amount) { }
queue.sync {
lockedFunds[gameID] amount
}
}
( : ) {
queue.sync {
lockedFunds.removeValue(forKey: gameID)
}
}
( : , : ) {
queue.async { [ ]
{ }
.main.async {
.balance amount
.recordTransaction(amount, reason: reason)
.saveBalance()
}
}
}
( : , : ) {
queue.async { [ ]
{ }
.main.async {
.balance amount
.recordTransaction(amount, reason: reason)
.saveBalance()
}
}
}
(: , : , : ) {
isLocalWinner (winner .current.identifierForVendor)
queue.sync {
lockedFunds.removeValue(forKey: gameID)
}
isLocalWinner {
addCoins(wager , reason: .gameWin(gameID: gameID))
} {
recordTransaction(wager, reason: .gameLoss(gameID: gameID))
}
}
() -> {
lastClaim .standard.double(forKey: )
now ().timeIntervalSince1970
now lastClaim {
}
addCoins(.dailyReward, reason: .dailyReward)
.standard.set(now, forKey: )
}
() {
.standard.set(balance, forKey: )
}
( : , : ) {
transaction (
id: (),
date: (),
amount: amount,
reason: reason
)
transactions.insert(transaction, at: )
transactions.count {
transactions (transactions.prefix())
}
saveTransactions()
}
() {
data ().encode(transactions) {
.standard.set(data, forKey: )
}
}
}
struct Transaction: Identifiable, Codable {
let id: UUID
let date: Date
let amount: Int // Positive = gain, negative = loss
let reason: TransactionReason
}
enum TransactionReason: Codable {
case dailyReward
case gameWin(gameID: UUID)
case gameLoss(gameID: UUID)
case achievement(name: String)
case spectatorWin(gameID: UUID)
case spectatorLoss(gameID: UUID)
var displayName: String {
switch self {
case .dailyReward: return "Daily Reward"
case .gameWin: return "Game Win"
case .gameLoss: return "Game Loss"
case .achievement(let name): return "Achievement: \(name)"
case .spectatorWin: return "Spectator Win"
case .spectatorLoss: return
}
}
}
func validateWager(_ amount: Int, opponentBalance: Int) -> Bool {
// Minimum
guard amount >= CurrencyConstants.minimumWager else {
return false
}
// Maximum = 10% of smaller balance
let maxWager = min(availableBalance, opponentBalance) / 10
guard amount <= maxWager else {
return false
}
return true
}
extension GameState {
func computeHash() -> String {
let data = "\(player1Balance)\(player2Balance)\(wager)\(timestamp)"
return SHA256.hash(data: data.data(using: .utf8)!)
.compactMap { String(format: "%02x", $0) }
.joined()
}
func isValid() -> Bool {
return stateHash == computeHash()
}
}
func validateGameState(_ state: GameState) throws {
// Check hash
guard state.isValid() else {
throw CurrencyError.tamperedState
}
// Check balances
guard state.player1Balance >= 0 && state.player2Balance >= 0 else {
throw CurrencyError.negativeBalance
}
// Check wager
guard state.wager >= CurrencyConstants.minimumWager else {
throw CurrencyError.invalidWager
}
}
struct SpectatorBet {
let spectatorID: UUID
let gameID: UUID
let predictedWinner: UUID
let amount: Int
}
func placeSpectatorBet(_ bet: SpectatorBet) -> Bool {
// Max 50% of player wager
let maxBet = currentGameWager / 2
guard bet.amount <= maxBet else {
return false
}
// Lock funds
return lockWager(bet.amount, for: bet.gameID)
}
func processSpectatorBets(gameID: UUID, winner: UUID) {
let bets = spectatorBets[gameID] ?? []
for bet in bets {
if bet.predictedWinner == winner {
// Correct prediction: 2x payout
addCoins(bet.amount * 2, reason: .spectatorWin(gameID: gameID))
} else {
// Wrong prediction: lose bet
recordTransaction(-bet.amount, reason: .spectatorLoss(gameID: gameID))
}
unlockWager(for: gameID)
}
}
class CurrencyManagerTests: XCTestCase {
var manager: CurrencyManager!
override func setUp() {
super.setUp()
manager = CurrencyManager.shared
// Reset to known state
manager.balance = 1000
}
func testWagerValidation() {
XCTAssertTrue(manager.canWager(10))
XCTAssertTrue(manager.canWager(100))
XCTAssertFalse(manager.canWager(5)) // Below minimum
XCTAssertFalse(manager.canWager(2000)) // Above balance
}
func testWagerLocking() {
let gameID = UUID()
XCTAssertTrue(manager.lockWager(100, for: gameID))
XCTAssertEqual(manager.availableBalance, 900)
// Can't lock more than available
XCTAssertFalse(manager.lockWager(950, for: UUID()))
}
func testGameResultProcessing() async {
let gameID = UUID()
let winner = ()
wager
manager.lockWager(wager, for: gameID)
manager.processGameResult(
gameID: gameID,
winner: winner,
wager: wager
)
(manager.balance, )
}
() {
state (
player1Balance: ,
player2Balance: ,
wager: ,
timestamp: ()
)
hash state.computeHash()
(hash.isEmpty)
(hash.count, )
}
}
<key>NSHumanReadableCopyright</key>
<string>Virtual currency for entertainment only. No real money wagering.</string>
struct DisclaimerView: View {
var body: some View {
VStack {
Text("Virtual Currency Only")
.font(.headline)
Text("Prop Coins have no real-world value and cannot be exchanged for money.")
.font(.caption)
.foregroundColor(.secondary)
}
.padding()
}
}