fix game and add playbook

This commit is contained in:
SamKry
2025-12-16 12:25:44 +01:00
parent 773fc76e1c
commit 2711b6ab87
2 changed files with 751 additions and 348 deletions

View File

@@ -9,420 +9,295 @@ contract GameMinusOne {
enum Moves { None, Rock, Paper, Scissors }
enum Outcomes { None, A, B, D, AT, BT }
enum GamePhase { Reg, InitC, FirstR, WithdC, WithdR, FinalC, FinalR, Done }
enum GamePhase { Reg, InitC, FirstR, WithdC, WithdR, Done }
struct Player {
address payable addr;
uint bet;
bytes32 e1;
bytes32 e2;
Moves m1;
Moves m2;
bytes32 ew;
uint w;
bytes32 ef;
Moves mf;
string n;
bytes32 hash1;
bytes32 hash2;
Moves move1;
Moves move2;
bytes32 wHash;
uint withdrawn;
string nick;
}
struct GameState {
Player a;
Player b;
Outcomes o;
GamePhase p;
uint fc;
uint fr;
uint fwc;
uint fwr;
uint ffc;
uint ffr;
uint ib;
uint id;
bool act;
Player pA;
Player pB;
Outcomes outcome;
GamePhase phase;
uint tCommit;
uint tReveal;
uint tWithdC;
uint tWithdR;
uint bet;
uint gameId;
bool active;
string mode;
}
mapping(uint => GameState) private g;
uint[] private ids;
mapping(uint => GameState) private games;
uint[] private gameIds;
uint private nextId = 1;
modifier validBet(uint id_) {
require(msg.value >= BET_MIN, "min");
require(g[id_].ib == 0 || msg.value == g[id_].ib, "bet");
modifier validBet(uint gameId) {
require(msg.value >= BET_MIN, "Bet below minimum");
require(games[gameId].bet == 0 || msg.value == games[gameId].bet, "Bet must match initial");
_;
}
function register(uint id_, string memory n_) public payable validBet(id_) returns (uint, uint) {
if (id_ == 0) id_ = createNewGame();
require(g[id_].act, "act");
require(g[id_].p == GamePhase.Reg, "st");
require(bytes(n_).length > 0 && bytes(n_).length <= 20, "n");
GameState storage gm = g[id_];
if (gm.a.addr == address(0)) {
gm.a.addr = payable(msg.sender);
gm.a.n = n_;
gm.ib = msg.value;
return (1, id_);
} else if (gm.b.addr == address(0)) {
require(msg.sender != gm.a.addr, "self");
gm.b.addr = payable(msg.sender);
gm.b.n = n_;
gm.p = GamePhase.InitC;
return (2, id_);
function register(uint gameId, string memory name) public payable validBet(gameId) returns (uint, uint) {
if (gameId == 0) gameId = createNewGame();
require(games[gameId].active, "Game not active");
require(games[gameId].phase == GamePhase.Reg, "Game started");
require(bytes(name).length > 0 && bytes(name).length <= 20, "Invalid nickname");
GameState storage game = games[gameId];
if (game.pA.addr == address(0)) {
game.pA.addr = payable(msg.sender);
game.pA.nick = name;
game.bet = msg.value;
return (1, gameId);
} else if (game.pB.addr == address(0)) {
require(msg.sender != game.pA.addr, "Cannot play yourself");
game.pB.addr = payable(msg.sender);
game.pB.nick = name;
game.phase = GamePhase.InitC;
return (2, gameId);
}
revert("full");
revert("Game full");
}
function createNewGame() private returns (uint) {
uint id_ = nextId++;
g[id_].id = id_;
g[id_].act = true;
g[id_].p = GamePhase.Reg;
g[id_].mode = "minusone";
ids.push(id_);
return id_;
uint gameId = nextId++;
games[gameId].gameId = gameId;
games[gameId].active = true;
games[gameId].phase = GamePhase.Reg;
games[gameId].mode = "minusone";
gameIds.push(gameId);
return gameId;
}
modifier isRegistered(uint id_) {
require(id_ != 0, "id");
require(msg.sender == g[id_].a.addr || msg.sender == g[id_].b.addr, "reg");
modifier isRegistered(uint gameId) {
require(gameId != 0, "Invalid game ID");
require(msg.sender == games[gameId].pA.addr || msg.sender == games[gameId].pB.addr, "Not registered");
_;
}
function doCommit(uint gameId, bytes32 hash1, bytes32 hash2, uint8 mode) private {
GameState storage game = games[gameId];
require(game.active && hash1 != bytes32(0), "Invalid hash");
bool isPlayerA = msg.sender == game.pA.addr;
if (mode == 0) {
require(game.phase == GamePhase.InitC, "Wrong phase");
require(hash2 != bytes32(0) && hash1 != hash2, "Moves must differ");
if (game.tCommit != 0) require(block.timestamp <= game.tCommit + COMMIT_TIMEOUT, "Commit timeout");
if (game.tCommit == 0) game.tCommit = block.timestamp;
if (isPlayerA) {
require(game.pA.hash1 == bytes32(0), "Already committed");
game.pA.hash1 = hash1; game.pA.hash2 = hash2;
} else {
require(game.pB.hash1 == bytes32(0), "Already committed");
game.pB.hash1 = hash1; game.pB.hash2 = hash2;
}
if (game.pA.hash1 != bytes32(0) && game.pB.hash1 != bytes32(0)) { game.phase = GamePhase.FirstR; game.tReveal = 0; }
} else {
require(game.phase == GamePhase.WithdC, "Wrong phase");
if (game.tWithdC != 0) require(block.timestamp <= game.tWithdC + COMMIT_TIMEOUT, "Commit timeout");
if (game.tWithdC == 0) game.tWithdC = block.timestamp;
if (isPlayerA) {
require(game.pA.wHash == bytes32(0), "Already committed");
game.pA.wHash = hash1;
} else {
require(game.pB.wHash == bytes32(0), "Already committed");
game.pB.wHash = hash1;
}
if (game.pA.wHash != bytes32(0) && game.pB.wHash != bytes32(0)) { game.phase = GamePhase.WithdR; game.tWithdR = 0; }
}
}
function commitInitialMoves(uint id_, bytes32 e1_, bytes32 e2_) public isRegistered(id_) returns (bool) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.InitC, "ph");
require(e1_ != bytes32(0) && e2_ != bytes32(0), "0");
require(e1_ != e2_, "eq");
if (gm.fc != 0) require(block.timestamp <= gm.fc + COMMIT_TIMEOUT, "to");
if (gm.fc == 0) gm.fc = block.timestamp;
if (msg.sender == gm.a.addr) {
require(gm.a.e1 == bytes32(0), "a");
gm.a.e1 = e1_;
gm.a.e2 = e2_;
} else if (msg.sender == gm.b.addr) {
require(gm.b.e1 == bytes32(0), "b");
gm.b.e1 = e1_;
gm.b.e2 = e2_;
} else revert("reg");
if (gm.a.e1 != bytes32(0) && gm.b.e1 != bytes32(0)) {
gm.p = GamePhase.FirstR;
gm.fr = 0;
}
doCommit(id_, e1_, e2_, 0);
return true;
}
function revealInitialMoves(uint id_, string memory c1, string memory c2) public isRegistered(id_) returns (Moves, Moves) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.FirstR, "ph");
if (gm.fr != 0) require(block.timestamp <= gm.fr + REVEAL_TIMEOUT, "to");
bytes32 e1_ = keccak256(abi.encodePacked(c1));
bytes32 e2_ = keccak256(abi.encodePacked(c2));
Moves m1_ = Moves(getFirstChar(c1));
Moves m2_ = Moves(getFirstChar(c2));
require(m1_ != Moves.None && m2_ != Moves.None, "m");
require(m1_ != m2_, "eq");
if (msg.sender == gm.a.addr) {
require(e1_ == gm.a.e1 && e2_ == gm.a.e2, "h");
gm.a.m1 = m1_;
gm.a.m2 = m2_;
} else if (msg.sender == gm.b.addr) {
require(e1_ == gm.b.e1 && e2_ == gm.b.e2, "h");
gm.b.m1 = m1_;
gm.b.m2 = m2_;
} else revert("reg");
if (gm.fr == 0) gm.fr = block.timestamp;
if (gm.a.m1 != Moves.None && gm.b.m1 != Moves.None) {
gm.p = GamePhase.WithdC;
gm.fwc = 0;
function revealInitialMoves(uint gameId, string memory clear1, string memory clear2) public isRegistered(gameId) returns (Moves, Moves) {
GameState storage game = games[gameId];
require(game.active, "Game not active");
require(game.phase == GamePhase.FirstR, "Wrong phase");
if (game.tReveal != 0) require(block.timestamp <= game.tReveal + REVEAL_TIMEOUT, "Reveal timeout");
bytes32 hash1 = keccak256(abi.encodePacked(clear1));
bytes32 hash2 = keccak256(abi.encodePacked(clear2));
Moves move1 = Moves(getFirstChar(clear1));
Moves move2 = Moves(getFirstChar(clear2));
require(move1 != Moves.None && move2 != Moves.None, "Invalid moves");
require(move1 != move2, "Moves must differ");
if (msg.sender == game.pA.addr) {
require(hash1 == game.pA.hash1 && hash2 == game.pA.hash2, "Hash mismatch");
game.pA.move1 = move1;
game.pA.move2 = move2;
} else if (msg.sender == game.pB.addr) {
require(hash1 == game.pB.hash1 && hash2 == game.pB.hash2, "Hash mismatch");
game.pB.move1 = move1;
game.pB.move2 = move2;
} else revert("Not registered");
if (game.tReveal == 0) game.tReveal = block.timestamp;
if (game.pA.move1 != Moves.None && game.pB.move1 != Moves.None) {
game.phase = GamePhase.WithdC;
game.tWithdC = 0;
}
return (m1_, m2_);
return (move1, move2);
}
function commitWithdraw(uint id_, bytes32 ew_) public isRegistered(id_) returns (bool) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.WithdC, "ph");
require(ew_ != bytes32(0), "0");
if (gm.fwc != 0) require(block.timestamp <= gm.fwc + COMMIT_TIMEOUT, "to");
if (msg.sender == gm.a.addr) {
require(gm.a.ew == bytes32(0), "a");
gm.a.ew = ew_;
} else if (msg.sender == gm.b.addr) {
require(gm.b.ew == bytes32(0), "b");
gm.b.ew = ew_;
} else revert("reg");
if (gm.fwc == 0) gm.fwc = block.timestamp;
if (gm.a.ew != bytes32(0) && gm.b.ew != bytes32(0)) {
gm.p = GamePhase.WithdR;
gm.fwr = 0;
}
function commitWithdraw(uint gameId, bytes32 wHash) public isRegistered(gameId) returns (bool) {
doCommit(gameId, wHash, bytes32(0), 1);
return true;
}
function withdrawMove(uint id_, string memory cw) public isRegistered(id_) returns (uint) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.WithdR, "ph");
if (gm.fwr != 0) require(block.timestamp <= gm.fwr + REVEAL_TIMEOUT, "to");
bytes32 ew_ = keccak256(abi.encodePacked(cw));
uint idx = getFirstChar(cw);
require(idx == 1 || idx == 2, "idx");
if (msg.sender == gm.a.addr) {
require(ew_ == gm.a.ew, "h");
require(gm.a.w == 0, "a");
gm.a.w = idx;
} else if (msg.sender == gm.b.addr) {
require(ew_ == gm.b.ew, "h");
require(gm.b.w == 0, "b");
gm.b.w = idx;
} else revert("reg");
if (gm.fwr == 0) gm.fwr = block.timestamp;
if (gm.a.w != 0 && gm.b.w != 0) {
gm.p = GamePhase.FinalC;
gm.ffc = 0;
}
function withdrawMove(uint gameId, string memory clear) public isRegistered(gameId) returns (uint) {
GameState storage game = games[gameId];
require(game.active, "Game not active");
require(game.phase == GamePhase.WithdR, "Wrong phase");
if (game.tWithdR != 0) require(block.timestamp <= game.tWithdR + REVEAL_TIMEOUT, "Reveal timeout");
bytes32 wHash = keccak256(abi.encodePacked(clear));
uint idx = getFirstChar(clear);
require(idx == 1 || idx == 2, "Index must be 1 or 2");
if (msg.sender == game.pA.addr) {
require(wHash == game.pA.wHash, "Hash mismatch");
require(game.pA.withdrawn == 0, "Already withdrew");
game.pA.withdrawn = idx;
} else if (msg.sender == game.pB.addr) {
require(wHash == game.pB.wHash, "Hash mismatch");
require(game.pB.withdrawn == 0, "Already withdrew");
game.pB.withdrawn = idx;
} else revert("Not registered");
if (game.tWithdR == 0) game.tWithdR = block.timestamp;
if (game.pA.withdrawn != 0 && game.pB.withdrawn != 0) determineOutcome(gameId);
return idx;
}
function commitFinalMove(uint id_, bytes32 ef_) public isRegistered(id_) returns (bool) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.FinalC, "ph");
require(ef_ != bytes32(0), "0");
if (gm.ffc != 0) require(block.timestamp <= gm.ffc + COMMIT_TIMEOUT, "to");
if (msg.sender == gm.a.addr) {
require(gm.a.ef == bytes32(0), "a");
gm.a.ef = ef_;
} else if (msg.sender == gm.b.addr) {
require(gm.b.ef == bytes32(0), "b");
gm.b.ef = ef_;
} else revert("reg");
if (gm.ffc == 0) gm.ffc = block.timestamp;
if (gm.a.ef != bytes32(0) && gm.b.ef != bytes32(0)) {
gm.p = GamePhase.FinalR;
gm.ffr = 0;
}
return true;
function determineOutcome(uint gameId) private {
GameState storage game = games[gameId];
Moves finalA = game.pA.withdrawn == 1 ? game.pA.move2 : game.pA.move1;
Moves finalB = game.pB.withdrawn == 1 ? game.pB.move2 : game.pB.move1;
if (finalA == finalB) game.outcome = Outcomes.D;
else if ((finalA == Moves.Rock && finalB == Moves.Scissors) ||
(finalA == Moves.Paper && finalB == Moves.Rock) ||
(finalA == Moves.Scissors && finalB == Moves.Paper))
game.outcome = Outcomes.A;
else game.outcome = Outcomes.B;
game.phase = GamePhase.Done;
}
function revealFinalMove(uint id_, string memory cf_) public isRegistered(id_) returns (Moves) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.FinalR, "ph");
if (gm.ffr != 0) require(block.timestamp <= gm.ffr + REVEAL_TIMEOUT, "to");
bytes32 ef_ = keccak256(abi.encodePacked(cf_));
Moves mf_ = Moves(getFirstChar(cf_));
require(mf_ != Moves.None, "m");
if (msg.sender == gm.a.addr) {
require(ef_ == gm.a.ef, "h");
Moves exp = gm.a.w == 1 ? gm.a.m2 : gm.a.m1;
require(mf_ == exp, "w");
gm.a.mf = mf_;
} else if (msg.sender == gm.b.addr) {
require(ef_ == gm.b.ef, "h");
Moves exp = gm.b.w == 1 ? gm.b.m2 : gm.b.m1;
require(mf_ == exp, "w");
gm.b.mf = mf_;
} else revert("reg");
if (gm.ffr == 0) gm.ffr = block.timestamp;
if (gm.a.mf != Moves.None && gm.b.mf != Moves.None) determineOutcome(id_);
return mf_;
}
function determineOutcome(uint id_) private {
GameState storage gm = g[id_];
if (gm.a.mf == gm.b.mf) gm.o = Outcomes.D;
else if ((gm.a.mf == Moves.Rock && gm.b.mf == Moves.Scissors) ||
(gm.a.mf == Moves.Paper && gm.b.mf == Moves.Rock) ||
(gm.a.mf == Moves.Scissors && gm.b.mf == Moves.Paper))
gm.o = Outcomes.A;
else gm.o = Outcomes.B;
gm.p = GamePhase.Done;
}
function getFirstChar(string memory s) private pure returns (uint) {
bytes memory b = bytes(s);
function getFirstChar(string memory str) private pure returns (uint) {
bytes memory b = bytes(str);
if (b.length == 0) return 0;
bytes1 f = b[0];
if (f == 0x31) return 1;
if (f == 0x32) return 2;
if (f == 0x33) return 3;
bytes1 first = b[0];
if (first == 0x31) return 1;
if (first == 0x32) return 2;
if (first == 0x33) return 3;
return 0;
}
function getOutcome(uint id_) public returns (Outcomes) {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.Done, "dn");
require(gm.o != Outcomes.None, "o");
address payable a = gm.a.addr;
address payable b = gm.b.addr;
uint bet = gm.ib;
Outcomes out = gm.o;
resetGame(id_);
pay(a, b, bet, out);
return out;
function getOutcome(uint gameId) public returns (Outcomes) {
GameState storage game = games[gameId];
require(game.active, "Game not active");
require(game.phase == GamePhase.Done, "Game not finished");
require(game.outcome != Outcomes.None, "No outcome yet");
address payable addrA = game.pA.addr;
address payable addrB = game.pB.addr;
uint betAmount = game.bet;
Outcomes result = game.outcome;
resetGame(gameId);
pay(addrA, addrB, betAmount, result);
return result;
}
function pay(address payable a, address payable b, uint bet, Outcomes o) private {
if (o == Outcomes.A) a.transfer(bet * 2);
else if (o == Outcomes.B) b.transfer(bet * 2);
else { a.transfer(bet); b.transfer(bet); }
function pay(address payable addrA, address payable addrB, uint bet, Outcomes outcome) private {
if (outcome == Outcomes.A) addrA.transfer(bet * 2);
else if (outcome == Outcomes.B) addrB.transfer(bet * 2);
else { addrA.transfer(bet); addrB.transfer(bet); }
}
function payWithSlash(address payable w, address payable, uint bet) private {
w.transfer(bet * 2);
function payWithSlash(address payable winner, address payable, uint bet) private {
winner.transfer(bet * 2);
}
function resetGame(uint id_) private {
g[id_].act = false;
function resetGame(uint gameId) private {
games[gameId].active = false;
}
function getContractBalance() public view returns (uint) {
return address(this).balance;
}
function whoAmI(uint id_) public view returns (uint) {
if (id_ == 0) return 0;
GameState storage gm = g[id_];
if (msg.sender == gm.a.addr) return 1;
if (msg.sender == gm.b.addr) return 2;
function whoAmI(uint gameId) public view returns (uint) {
if (gameId == 0) return 0;
GameState storage game = games[gameId];
if (msg.sender == game.pA.addr) return 1;
if (msg.sender == game.pB.addr) return 2;
return 0;
}
function getGamePhase(uint id_) public view returns (GamePhase) {
return g[id_].p;
function getGamePhase(uint gameId) public view returns (GamePhase) {
return games[gameId].phase;
}
function getTimeLeft(uint id_) public view returns (int) {
if (id_ == 0) return 0;
GameState storage gm = g[id_];
if (gm.p == GamePhase.InitC && gm.fc != 0) {
uint d = gm.fc + COMMIT_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
} else if (gm.p == GamePhase.FirstR && gm.fr != 0) {
uint d = gm.fr + REVEAL_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
} else if (gm.p == GamePhase.WithdC && gm.fwc != 0) {
uint d = gm.fwc + COMMIT_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
} else if (gm.p == GamePhase.WithdR && gm.fwr != 0) {
uint d = gm.fwr + REVEAL_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
} else if (gm.p == GamePhase.FinalC && gm.ffc != 0) {
uint d = gm.ffc + COMMIT_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
} else if (gm.p == GamePhase.FinalR && gm.ffr != 0) {
uint d = gm.ffr + REVEAL_TIMEOUT;
if (block.timestamp >= d) return 0;
return int(d - block.timestamp);
function getTimeLeft(uint gameId) public view returns (int) {
if (gameId == 0) return 0;
GameState storage game = games[gameId];
uint timeout; uint start;
if (game.phase == GamePhase.InitC && game.tCommit != 0) { timeout = COMMIT_TIMEOUT; start = game.tCommit; }
else if (game.phase == GamePhase.FirstR && game.tReveal != 0) { timeout = REVEAL_TIMEOUT; start = game.tReveal; }
else if (game.phase == GamePhase.WithdC && game.tWithdC != 0) { timeout = COMMIT_TIMEOUT; start = game.tWithdC; }
else if (game.phase == GamePhase.WithdR && game.tWithdR != 0) { timeout = REVEAL_TIMEOUT; start = game.tWithdR; }
else return int(COMMIT_TIMEOUT);
uint deadline = start + timeout;
if (block.timestamp >= deadline) return 0;
return int(deadline - block.timestamp);
}
function resolveTimeout(uint gameId) public isRegistered(gameId) {
GameState storage game = games[gameId];
require(game.active, "Game not active");
address caller = msg.sender;
bool isPlayerA = caller == game.pA.addr;
bool timedOut = false;
if (game.phase == GamePhase.InitC && game.tCommit != 0 && block.timestamp > game.tCommit + COMMIT_TIMEOUT) {
timedOut = (isPlayerA && game.pB.hash1 == bytes32(0)) || (!isPlayerA && game.pA.hash1 == bytes32(0));
} else if (game.phase == GamePhase.FirstR && game.tReveal != 0 && block.timestamp > game.tReveal + REVEAL_TIMEOUT) {
timedOut = (isPlayerA && game.pB.move1 == Moves.None) || (!isPlayerA && game.pA.move1 == Moves.None);
} else if (game.phase == GamePhase.WithdC && game.tWithdC != 0 && block.timestamp > game.tWithdC + COMMIT_TIMEOUT) {
timedOut = (isPlayerA && game.pB.wHash == bytes32(0)) || (!isPlayerA && game.pA.wHash == bytes32(0));
} else if (game.phase == GamePhase.WithdR && game.tWithdR != 0 && block.timestamp > game.tWithdR + REVEAL_TIMEOUT) {
timedOut = (isPlayerA && game.pB.withdrawn == 0) || (!isPlayerA && game.pA.withdrawn == 0);
}
return int(COMMIT_TIMEOUT);
require(timedOut, "No timeout or invalid caller");
game.outcome = isPlayerA ? Outcomes.A : Outcomes.B;
address payable winner = payable(caller);
address payable loser = isPlayerA ? game.pB.addr : game.pA.addr;
uint betAmount = game.bet;
resetGame(gameId);
payWithSlash(winner, loser, betAmount);
}
function resolveTimeout(uint id_) public isRegistered(id_) {
GameState storage gm = g[id_];
require(gm.act, "act");
address c = msg.sender;
address payable w = payable(c);
bool to = false;
if (gm.p == GamePhase.InitC && gm.fc != 0) {
if (block.timestamp > gm.fc + COMMIT_TIMEOUT) {
if (c == gm.a.addr && gm.b.e1 == bytes32(0)) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.e1 == bytes32(0)) {
gm.o = Outcomes.B;
to = true;
}
}
} else if (gm.p == GamePhase.FirstR && gm.fr != 0) {
if (block.timestamp > gm.fr + REVEAL_TIMEOUT) {
if (c == gm.a.addr && gm.b.m1 == Moves.None) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.m1 == Moves.None) {
gm.o = Outcomes.B;
to = true;
}
}
} else if (gm.p == GamePhase.WithdC && gm.fwc != 0) {
if (block.timestamp > gm.fwc + COMMIT_TIMEOUT) {
if (c == gm.a.addr && gm.b.ew == bytes32(0)) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.ew == bytes32(0)) {
gm.o = Outcomes.B;
to = true;
}
}
} else if (gm.p == GamePhase.WithdR && gm.fwr != 0) {
if (block.timestamp > gm.fwr + REVEAL_TIMEOUT) {
if (c == gm.a.addr && gm.b.w == 0) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.w == 0) {
gm.o = Outcomes.B;
to = true;
}
}
} else if (gm.p == GamePhase.FinalC && gm.ffc != 0) {
if (block.timestamp > gm.ffc + COMMIT_TIMEOUT) {
if (c == gm.a.addr && gm.b.ef == bytes32(0)) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.ef == bytes32(0)) {
gm.o = Outcomes.B;
to = true;
}
}
} else if (gm.p == GamePhase.FinalR && gm.ffr != 0) {
if (block.timestamp > gm.ffr + REVEAL_TIMEOUT) {
if (c == gm.a.addr && gm.b.mf == Moves.None) {
gm.o = Outcomes.A;
to = true;
} else if (c == gm.b.addr && gm.a.mf == Moves.None) {
gm.o = Outcomes.B;
to = true;
}
}
}
require(to, "to");
address payable l = w == gm.a.addr ? gm.b.addr : gm.a.addr;
uint bet = gm.ib;
resetGame(id_);
payWithSlash(w, l, bet);
}
function getGameDetails(uint id_) public view returns (
function getGameDetails(uint gameId) public view returns (
Player memory, Player memory, uint, Outcomes, GamePhase, bool, uint, string memory) {
GameState storage gm = g[id_];
require(gm.id != 0, "no");
return (gm.a, gm.b, gm.ib, gm.o, gm.p, gm.act, gm.id, gm.mode);
GameState storage game = games[gameId];
require(game.gameId != 0, "Game does not exist");
return (game.pA, game.pB, game.bet, game.outcome, game.phase, game.active, game.gameId, game.mode);
}
function getActiveGameIds() public view returns (uint[] memory) {
uint c = 0;
for (uint i = 0; i < ids.length; i++) if (g[ids[i]].act) c++;
uint[] memory a = new uint[](c);
uint ix = 0;
for (uint i = 0; i < ids.length; i++) if (g[ids[i]].act) a[ix++] = ids[i];
return a;
}
function startGame(uint id_) public {
GameState storage gm = g[id_];
require(gm.act, "act");
require(gm.p == GamePhase.Reg, "st");
require(gm.a.addr != address(0) && gm.b.addr != address(0), "r");
gm.p = GamePhase.InitC;
uint count = 0;
for (uint i = 0; i < gameIds.length; i++) if (games[gameIds[i]].active) count++;
uint[] memory active = new uint[](count);
uint idx = 0;
for (uint i = 0; i < gameIds.length; i++) if (games[gameIds[i]].active) active[idx++] = gameIds[i];
return active;
}
}