Skip to content

Instantly share code, notes, and snippets.

@hsjunnesson
Created January 10, 2018 15:44
Show Gist options
  • Save hsjunnesson/2880f8df6e44c9d9042331b5b2bf66a3 to your computer and use it in GitHub Desktop.
Save hsjunnesson/2880f8df6e44c9d9042331b5b2bf66a3 to your computer and use it in GitHub Desktop.
Tic Tac Toe on Ethereum
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