Skip to content

Instantly share code, notes, and snippets.

@drewhoener
Created May 4, 2020 16:00
Show Gist options
  • Save drewhoener/3e64d5669d34ba085130362830f60e0a to your computer and use it in GitHub Desktop.
Save drewhoener/3e64d5669d34ba085130362830f60e0a to your computer and use it in GitHub Desktop.
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