游戏逻辑

Adrift 如何利用链上签到和随机性打造一款最后一名玩家生存的游戏。

Adrift 是一款链上生存游戏,玩家必须定期签到以避免被淘汰。所有游戏逻辑和随机性均由智能合约执行,管理员可以配置参数如间隔时间、宽限期和结果概率。这确保了游戏的公平性、透明性和趣味性。最后剩下的玩家将获胜。

玩家的游戏体验如下:

  1. 在游戏开始前注册。
  2. 定期签到以继续留在游戏中。
  3. 每次签到(由 AdriftCheckInOutcomes 合约处理)会有一个随机结果:增益、减益或淘汰。
  4. 未签到或被淘汰即出局。
  5. 最后剩下的玩家获胜。

注册

// Key lines from register()
nextCheckIn[player] = CHECKIN_INITIALIZED;
playerCount++;
if (address(this).balance >= REGISTRATION_GAS) {
    Address.sendValue(payable(player), REGISTRATION_GAS);
}
  1. 在游戏开始前(在 gameStartTime 之前)。
  2. 玩家通过调用 Adrift 合约中的 register 函数进行注册。
  3. 初始化玩家的下一次签到时间。
  4. 玩家数量增加。
  5. 如果合约有足够的余额,玩家会收到注册的 gas 奖励(默认 1 个代币),以应用链的自定义 gas 代币支付。自定义 gas 代币使补贴变得简单且低成本。
  6. 触发一个包含注册详情的事件。

游戏开始

// Key lines from setGameStartTime()
gameStartTime = startTime;
emit GameStartTimeSet(startTime);
  1. 游戏在预定义的 gameStartTime 时间开始(由管理员设置)。
  2. 在此时间之前不允许签到。

签到

// Key lines from checkIn()
int256 outcome = checkInOutcomes.getOutcome(msg.sender);
if (outcome == checkInOutcomes.DISQUALIFIED_OUTCOME()) {
    return disqualifyFromCheckIn(msg.sender, outcome);
}
nextCheckIn[msg.sender] = nextCheckInTime;
emit PlayerCheckedIn(msg.sender, block.timestamp, buffOrDebuff, nextCheckInTime, false);
  1. 在游戏期间,玩家必须在其 nextCheckInTime 到期之前调用 checkIn
  2. 每次签到的结果由 CheckInOutcomes 合约决定,可能是增益、减益或淘汰。
  3. 如果被淘汰,玩家将被移出游戏。
  4. 根据结果更新下一次签到时间。
  5. 触发一个包含签到结果的事件。

取消资格

// Key lines from disqualifyInactivePlayer/disqualifyFromCheckIn
_disqualify(player, nextCheckInTime);
nextCheckIn[player] = CHECKIN_DISQUALIFIED;
isPlayerDQed[player] = true;
  1. 如果玩家错过了签到时间窗口,任何人都可以在 Adrift 合约中调用 disqualifyInactivePlayer 将其移除。
  2. 签到期间也可能随机发生取消资格的情况。
  3. 玩家状态会更新为取消资格,并被排除在游戏之外。
  4. 系统会触发一个取消资格的事件。

我们运行一个简单的定时任务,定期调用心跳端点,自动取消不活跃玩家的资格,并在仅剩一名活跃玩家时结束游戏。

结果与随机性

// Key lines from CheckInOutcomes.sol
uint256 rand = uint256(keccak256(abi.encodePacked(random.random(), playerNonces[player]++, player)));
if (rand % PRECISION < DISQUALIFICATION_CHANCE) {
    return DISQUALIFIED_OUTCOME;
}
uint256 outcome = (rand % OUTCOME_RANGE) + 1;
bool isNegative = (rand >> 128) % 2 == 0;
return isNegative ? -int256(outcome) : int256(outcome);
// Key lines from Random.sol
uint256 public random;
function setRandom(uint256 _random) external onlyRandomnessAdmin {
    random = _random;
}

签到结果由 CheckInOutcomes 合约决定,该合约通过 Random 合约在每个区块中使用新的由排序器注入的随机性。当玩家签到时:

  1. 合约会结合最新的随机值、玩家地址和一个随机数生成器(nonce)生成一个唯一的随机数。
  2. 玩家有一定的概率(默认:2%)会立即被取消资格。
  3. 否则,会在设定范围内(默认:1–24)生成一个随机结果(增益或减益),其符号(正/负)也会随机选择。
  4. Random 合约的值由排序器设置,确保不可预测性和可审计性。

该系统确保每次签到都是不可预测的、公平的,并且可以在链上验证。

胜利与游戏结束

// Key lines from endGame()
if (playerCount == 1) {
    winner = player;
}
gameEndTime = block.timestamp;
emit GameEnded(gameEndTime, winner);
  1. 最后一名活跃玩家将被宣布为胜利者。
  2. 如果所有玩家都被取消资格,则没有胜利者。
  3. 游戏结束时间和胜利者将被记录并触发一个事件。