Created
January 10, 2018 15:44
-
-
Save hsjunnesson/2880f8df6e44c9d9042331b5b2bf66a3 to your computer and use it in GitHub Desktop.
Tic Tac Toe on Ethereum
This file contains 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
pragma solidity ^0.4.0; | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address public owner; | |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
function Ownable() public { | |
owner = msg.sender; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(msg.sender == owner); | |
_; | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address newOwner) public onlyOwner { | |
require(newOwner != address(0)); | |
OwnershipTransferred(owner, newOwner); | |
owner = newOwner; | |
} | |
/** | |
* @dev Kills the contract, sending the remaining value back to the owner. | |
*/ | |
function kill() public onlyOwner { | |
selfdestruct(owner); // kills this contract and sends remaining funds back to creator | |
} | |
} | |
contract TicTacToe is Ownable { | |
uint8 private constant numWinners = 8; | |
uint8 private constant boardSize = 9; | |
uint private constant salt = 3758703259870465; | |
uint private minimumAnte = 100 * 1000000000000; // 100 Szabos, around 1 SEK. | |
uint private houseTax = 10; | |
enum Mark { Empty, X, O } | |
enum GameState { waiting, started, finished } | |
event GameWon(uint gameId, address winner); | |
event GameDraw(uint gameId); | |
struct Game { | |
Mark[boardSize] board; | |
address challenger; // Always plays X | |
address opponent; // Always plays O | |
address starting; // Player that starts. | |
uint ante; | |
GameState state; | |
} | |
Game[] public games; | |
/* -- Public -- */ | |
function TicTacToe() public { | |
} | |
/// Returns the minimum ante. | |
function getMinimumAnte() public view returns (uint) { | |
return minimumAnte; | |
} | |
/// Sets the minimum ante. | |
function setMinimumAnte(uint newAnte) public onlyOwner { | |
minimumAnte = newAnte; | |
} | |
/// Sets the house tax. | |
function setHouseTax(uint newHouseTax) public onlyOwner { | |
houseTax = newHouseTax; | |
} | |
/// Starts a new game, by challenging someone to play you. | |
function challenge() public payable returns (uint gameId) { | |
require(msg.value >= minimumAnte); | |
gameId = games.length++; | |
Game storage game = games[gameId]; | |
game.board = [Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty, Mark.Empty]; | |
game.challenger = msg.sender; | |
game.ante = msg.value; | |
game.state = GameState.waiting; | |
return gameId; | |
} | |
/// Returns the ante of a game. | |
function getAnte(uint gameId) public view returns (uint) { | |
Game memory game = games[gameId]; | |
return game.ante; | |
} | |
/// Responds to a challenge by joining the game as the opponent. | |
function join(uint gameId) public payable { | |
Game storage game = games[gameId]; | |
require(msg.value >= game.ante); | |
require(game.opponent == 0); | |
require(msg.sender != game.challenger); | |
require(game.state == GameState.waiting); | |
game.opponent = msg.sender; | |
game.starting = coinFlip(gameId) ? game.challenger : game.opponent; | |
game.state = GameState.started; | |
} | |
/// Plays your mark at index. | |
/// Only the next legal player may call. | |
function play(uint gameId, uint8 idx) public { | |
Game storage game = games[gameId]; | |
require(msg.sender == nextPlayer(gameId)); | |
require(idx < boardSize); | |
require(game.board[idx] == Mark.Empty); | |
Mark mark; | |
if (msg.sender == game.challenger) { | |
mark = Mark.X; | |
} else if (msg.sender == game.opponent) { | |
mark = Mark.O; | |
} | |
require(mark == Mark.X || mark == Mark.O); | |
game.board[idx] = mark; | |
address winner = getWinner(gameId); | |
uint tax = (game.ante * 2) / houseTax; | |
uint winnings = (game.ante * 2) - tax; | |
if (winner != 0 && game.state == GameState.started) { | |
game.state = GameState.finished; | |
GameWon(gameId, winner); | |
winner.transfer(winnings); | |
owner.transfer(tax); | |
} else if (isDraw(gameId)) { | |
game.state = GameState.finished; | |
GameDraw(gameId); | |
game.challenger.transfer(winnings/2); | |
game.opponent.transfer(winnings/2); | |
owner.transfer(tax); | |
} | |
} | |
/// Returns address of winner or 0 if no winner. | |
function getWinner(uint gameId) public view returns (address winner) { | |
Game memory game = games[gameId]; | |
uint8[3][numWinners] memory winners = [[0, 1, 2], [3, 4, 5], [6, 7, 8], | |
[0, 3, 6], [1, 4, 7], [2, 5, 8], | |
[0, 4, 8], [2, 4, 6]]; | |
winner = 0; | |
for (uint8 i = 0; i < numWinners; i++) { | |
uint8[3] memory positions = winners[i]; | |
Mark[3] memory marks = [game.board[positions[0]], game.board[positions[1]], game.board[positions[2]]]; | |
if (marks[0] == Mark.X && marks[1] == Mark.X && marks[2] == Mark.X) { | |
winner = game.challenger; | |
break; | |
} | |
if (marks[0] == Mark.O && marks[1] == Mark.O && marks[2] == Mark.O) { | |
winner = game.opponent; | |
break; | |
} | |
} | |
} | |
/// Whether the game is a draw. | |
function isDraw(uint gameId) public view returns (bool) { | |
Game memory game = games[gameId]; | |
uint8 emptyCount = 0; | |
for (uint8 i = 0; i < boardSize; i++) { | |
if (game.board[i] == Mark.Empty) { emptyCount += 1; } | |
} | |
return getWinner(gameId) == 0 && emptyCount == 0; | |
} | |
/// Queries whether it's your turn to play. | |
function isMyTurn(uint gameId) public view returns (bool) { | |
return msg.sender == nextPlayer(gameId); | |
} | |
/// Returns a string description of the game. | |
function getDescription(uint gameId) public view returns (string) { | |
Game memory game = games[gameId]; | |
bytes memory desc = new bytes(11); | |
uint8 descIndex = 0; | |
for (uint8 i = 0; i< boardSize; i++) { | |
byte b; | |
if (game.board[i] == Mark.Empty) { b = "-"; } | |
if (game.board[i] == Mark.X) { b = "X"; } | |
if (game.board[i] == Mark.O) { b = "O"; } | |
desc[descIndex++] = b; | |
if (i == 2 || i == 5) { | |
desc[descIndex++] = "\n"; | |
} | |
} | |
return string(desc); | |
} | |
/// Returns the player to play next. | |
function nextPlayer(uint gameId) public view returns (address) { | |
Game memory game = games[gameId]; | |
if (getWinner(gameId) != 0) { | |
return 0; | |
} | |
if (game.challenger == 0 || game.opponent == 0) { | |
return 0; | |
} | |
uint8 emptyCount = 0; | |
uint8 xCount = 0; | |
uint8 oCount = 0; | |
for (uint i = 0; i < boardSize; i++) { | |
if (game.board[i] == Mark.Empty) { emptyCount += 1; } | |
if (game.board[i] == Mark.X) { xCount += 1; } | |
if (game.board[i] == Mark.O) { oCount += 1; } | |
} | |
if (emptyCount == 0) { | |
return 0; | |
} | |
if (xCount == oCount) { | |
return game.starting; | |
} | |
if (xCount > oCount) { | |
return game.opponent; | |
} else { | |
return game.challenger; | |
} | |
} | |
/// Returns a random coinflip, true or false. | |
function coinFlip(uint seed) private pure returns (bool) { | |
return uint256(keccak256(seed+salt))%2 == 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment