Created
May 4, 2020 16:00
-
-
Save drewhoener/3e64d5669d34ba085130362830f60e0a to your computer and use it in GitHub Desktop.
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.6.2 <0.7.0; | |
// Implement the Nim board game (the "misere" version -- if you move last, you lose). | |
// See https://en.wikipedia.org/wiki/Nim for details about the game, but in essence | |
// there are N piles (think array length N of uint256) and in each turn a player must | |
// take 1 or more items (player chooses) from only 1 pile. If the pile is empty, of | |
// course the player cannot take items from it. The last player to take items loses. | |
// Also in our version, if a player makes an illegal move, it loses. | |
// To implement this game, you need to create a contract that implements the interface | |
// "Nim" (see below). | |
// Nim.startMisere kicks things off. It is given the addresses of 2 NimPlayer (contracts) | |
// and an initial board configuration (for example [1,2] would be 2 piles with 1 and 2 items in them). | |
// Your code must call the nextMove API alternately in each NimPlayer contract to receive | |
// that player's next move, ensure its legal, apply it, and see if the player lost. | |
// If the move is illegal, or the player lost, call the appropriate Uxxx functions for both players, | |
// and award the winner all the money sent into "startMisere" EXCEPT your 0.001 ether fee for hosting the game. | |
// I have supplied two example players. | |
// This file is available on moodle, no need to type it in yourself! | |
// Good luck! You've got this! | |
interface NimPlayer | |
{ | |
// Given a set of piles, return a pile number and a quantity of items to remove | |
function nextMove(uint256[] calldata piles) external returns (uint, uint256); | |
// Called if you win, with your winnings! | |
function Uwon() external payable; | |
// Called if you lost :-( | |
function Ulost() external; | |
// Called if you lost because you made an illegal move :-( | |
function UlostBadMove() external; | |
} | |
interface Nim | |
{ | |
// fee is 0.001 ether, award the winner the rest | |
function startMisere(NimPlayer a, NimPlayer b, uint256[] calldata piles) payable external; | |
} | |
contract NimBoard is Nim { | |
uint hostFee = 0.001 ether; | |
uint256[] board; | |
bool badMove = false; | |
uint numMoves; | |
uint move; | |
event debugBoard(uint256[] boardValue); | |
event logBadMove(uint index, uint number); | |
event badMoveMessage(uint code); | |
event goodMoveMessage(uint code); | |
function startMisere(NimPlayer playerOne, NimPlayer playerTwo, uint256[] calldata piles) payable external override { | |
require(msg.value >= hostFee); | |
emit debugBoard(piles); | |
board = piles; | |
emit debugBoard(board); | |
numMoves = 0; | |
move = 0; | |
badMove = false; | |
while(hasMovesLeft() && !badMove) { | |
if(move == 0) { | |
runMove(playerOne, playerTwo); | |
move = 1; | |
} else { | |
runMove(playerTwo, playerOne); | |
move = 0; | |
} | |
numMoves++; | |
} | |
if(!badMove) { | |
if(move == 0) { | |
playerOne.Uwon{value: msg.value - hostFee}(); | |
playerTwo.Ulost(); | |
} else { | |
playerTwo.Uwon{value: msg.value - hostFee}(); | |
playerOne.Ulost(); | |
} | |
} | |
} | |
function getWinnings() internal returns (uint) { | |
} | |
function runMove(NimPlayer curPlayer, NimPlayer otherPlayer) internal { | |
uint index; | |
uint number; | |
(index, number) = curPlayer.nextMove(board); | |
if(!goodMove(index, number)) { | |
emit logBadMove(index, number); | |
badMove = true; | |
curPlayer.UlostBadMove(); | |
otherPlayer.Uwon{value: msg.value - hostFee}(); | |
return; | |
} | |
board[index] -= number; | |
emit debugBoard(board); | |
} | |
function hasMovesLeft() internal view returns (bool) { | |
for(uint i = 0; i < board.length; i++) { | |
if(board[i] > 0) { | |
return (true); | |
} | |
} | |
return (false); | |
} | |
function goodMove(uint index, uint256 number) internal returns (bool) { | |
if(index >= board.length) { | |
emit badMoveMessage(0); | |
return (false); | |
} | |
if(number <= 0) { | |
emit badMoveMessage(1); | |
return (false); | |
} | |
if(board[index] < number) { | |
emit badMoveMessage(2); | |
return (false); | |
} | |
emit goodMoveMessage(0); | |
return (true); | |
} | |
function boardState() external view returns (uint256[] memory) { | |
return board; | |
} | |
function getNumMoves() external view returns (uint) { | |
return numMoves; | |
} | |
} | |
contract TrackingNimPlayer is NimPlayer | |
{ | |
uint losses=0; | |
uint wins=0; | |
uint faults=0; | |
// Given a set of piles, return a pile number and a quantity of items to remove | |
function nextMove(uint256[] calldata) virtual override external returns (uint, uint256) | |
{ | |
return(0,1); | |
} | |
// Called if you win, with your winnings! | |
function Uwon() override external payable | |
{ | |
wins += 1; | |
} | |
// Called if you lost :-( | |
function Ulost() override external | |
{ | |
losses += 1; | |
} | |
// Called if you lost because you made an illegal move :-( | |
function UlostBadMove() override external | |
{ | |
faults += 1; | |
} | |
function results() external view returns(uint, uint, uint, uint) | |
{ | |
return(wins, losses, faults, address(this).balance); | |
} | |
function reset() external { | |
wins = 0; | |
losses = 0; | |
faults = 0; | |
} | |
} | |
contract Boring1NimPlayer is TrackingNimPlayer | |
{ | |
// Given a set of piles, return a pile number and a quantity of items to remove | |
function nextMove(uint256[] calldata piles) override external returns (uint, uint256) | |
{ | |
for(uint i=0;i<piles.length; i++) | |
{ | |
if (piles[i]>1) return (i, piles[i]-1); // consumes all in a pile | |
} | |
for(uint i=0;i<piles.length; i++) | |
{ | |
if (piles[i]>0) return (i, piles[i]); // consumes all in a pile | |
} | |
} | |
} | |
/* | |
Test vectors: | |
deploy your contract NimBoard, call it C | |
deploy 2 Boring1NimPlayers, A & B | |
In remix set the value to 0.002 ether and call | |
c.startMisere(A,B,[1,1]) | |
A should have 1 win and a balance of 1000000000000000 (0.001 ether) | |
B should have 1 loss | |
Now try c.startMisere(A,B,[1,2]) | |
Now A and B should both have 1 win and 1 loss (and B should have gained however many coins you funded the round with) | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment