Skip to content

Commit

Permalink
add tictactoe
Browse files Browse the repository at this point in the history
  • Loading branch information
hh committed May 7, 2022
1 parent 1ffabc0 commit a009119
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 63 deletions.
44 changes: 20 additions & 24 deletions contracts/tictactoe.scrypt
Original file line number Diff line number Diff line change
@@ -1,48 +1,47 @@
import "arrayUtil.scrypt";

contract TicTacToe {



PubKey alice;
PubKey bob;

//Represents whether it is alice's turn to play chess
@state
bool is_alice_turn;

//Represents the position of the chessboard. For example, a chess piece with alice in the first row
//and first column is expressed as [1,0,0,0,0,0,0,0,0]
@state
bytes board;

int[9] board;

static const int TURNLEN = 1;
static const int BOARDLEN = 9;
static const bytes EMPTY = b'00';
static const bytes ALICE = b'01';
static const bytes BOB = b'02';
static const int EMPTY = 0;
static const int ALICE = 1;
static const int BOB = 2;

public function move(int n, Sig sig, int amount, SigHashPreimage txPreimage) {

require(Tx.checkPreimage(txPreimage));
require(n >= 0 && n < BOARDLEN);

bytes play = this.is_alice_turn ? ALICE : BOB;
// not filled
require(this.board[n] == EMPTY);

int play = this.is_alice_turn ? ALICE : BOB;
PubKey player = this.is_alice_turn ? this.alice : this.bob;

// ensure it's player's turn
require(checkSig(sig, player));
// make the move
this.board = ArrayUtil.setElemAt(this.board, n, play);
this.board[n] = play;
this.is_alice_turn = !this.is_alice_turn;


bytes outputs = b'';
if (this.won(this.board, play)) {
// winner takes all
if (this.won(play)) {
bytes outputScript = Utils.buildPublicKeyHashScript(hash160(player));
bytes output = Utils.buildOutput(outputScript, amount);
outputs = output;
}
else if (this.full(this.board)) {
// draw: equally split, i.e., both outputs have the same amount
else if (this.full()) {
bytes aliceScript = Utils.buildPublicKeyHashScript(hash160(this.alice));
bytes aliceOutput = Utils.buildOutput(aliceScript, amount);

Expand All @@ -52,7 +51,6 @@ contract TicTacToe {
outputs = aliceOutput + bobOutput;
}
else {
// update state
bytes scriptCode_ = this.getStateScript();
bytes output = Utils.buildOutput(scriptCode_, amount);
outputs = output;
Expand All @@ -61,16 +59,15 @@ contract TicTacToe {
require(hash256(outputs) == SigHash.hashOutputs(txPreimage));
}

// does play win after current move?
function won(bytes board, bytes play) : bool {
// three in a row, a column, or a diagnoal
function won(int play) : bool {

int[8][3] lines = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]];

bool anyLine = false;
loop (8) : i {
bool line = true;
loop (3) : j {
line = line && ArrayUtil.getElemAt(board, lines[i][j]) == play;
line = line && this.board[lines[i][j]] == play;
}

anyLine = anyLine || line;
Expand All @@ -79,12 +76,11 @@ contract TicTacToe {
return anyLine;
}

// is board full?
function full(bytes board) : bool {
function full() : bool {
bool full = true;

loop (BOARDLEN) : i {
full = full && ArrayUtil.getElemAt(board, i) != EMPTY;
full = full && this.board[i] != TicTacToe.EMPTY;
}

return full;
Expand Down
176 changes: 137 additions & 39 deletions tests/js/tictactoe.scrypttest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,93 +9,191 @@ const publicKey1 = bsv.PublicKey.fromPrivateKey(privateKey1)
const privateKey2 = new bsv.PrivateKey.fromRandom('testnet')
const publicKey2 = bsv.PublicKey.fromPrivateKey(privateKey2)

const privateKeyAlice = new bsv.PrivateKey.fromRandom('testnet')
const publicKeyAlice = bsv.PublicKey.fromPrivateKey(privateKeyAlice)

const privateKeyBob = new bsv.PrivateKey.fromRandom('testnet')
const publicKeyBob = bsv.PublicKey.fromPrivateKey(privateKeyBob)

const Tictactoe = buildContractClass(compileContract('tictactoe.scrypt'));

const game = new Tictactoe(new PubKey(toHex(publicKey1)), new PubKey(toHex(publicKey2)), true, new Bytes('000000000000000000'));
let game = new Tictactoe(new PubKey(toHex(publicKeyAlice)), new PubKey(toHex(publicKeyBob)), true, [0,0,0,0,0,0,0,0,0]);



describe('Test sCrypt contract Tictactoe In Javascript', () => {
let result, preimage, sig, prevLockingScript
let result, preimage, sig

function reset() {
game.board = [0,0,0,0,0,0,0,0,0];
game.is_alice_turn = true;
}

function run(n, newState) {
function moveScript(is_alice_turn, board) {
return {
outputScript: game.getNewStateScript({
is_alice_turn: is_alice_turn,
board: board
}),
is_alice_turn: is_alice_turn,
board: board
}

const tx = newTx();
const newLockingScript = game.getNewStateScript(newState);
}

function testMove(isAliceTurn, n, newStates, expected) {
const privateKey = isAliceTurn ? privateKeyAlice : privateKeyBob;


const tx = newTx();

tx.addOutput(new bsv.Transaction.Output({
script: newLockingScript,
script: newStates.outputScript,
satoshis: 10000
}))


preimage = getPreimage(tx, game.lockingScript, inputSatoshis);

sig = signTx(tx, !newState.is_alice_turn ? privateKey1 : privateKey2, game.lockingScript, inputSatoshis)
sig = signTx(tx, privateKey, game.lockingScript, inputSatoshis)

const context = { tx, inputIndex, inputSatoshis }

result = game.move(n, new Sig(toHex(sig)), 10000, preimage).verify(context)
expect(result.success, result.error).to.be.true;

//update state
game.is_alice_turn = newState.is_alice_turn;
game.board = newState.board;
if (expected === false) {
expect(result.success, result.error).to.be.false;
} else {
expect(result.success, result.error).to.be.true;
game.is_alice_turn = newStates.is_alice_turn;
game.board = newStates.board;
}

}

it('n = 0', () => {
run(0, {
is_alice_turn: false,
board: new Bytes('010000000000000000')
});
});
function testMoveWin(isAliceTurn, n, outputScript) {
const privateKey = isAliceTurn ? privateKeyAlice : privateKeyBob;


it('n = 4', () => {
run(4, {
is_alice_turn: true,
board: new Bytes('010000000200000000')
});
});
const tx = newTx();

tx.addOutput(new bsv.Transaction.Output({
script: outputScript,
satoshis: 10000
}))

it('n = 1', () => {

run(1, {
is_alice_turn: false,
board: new Bytes('010100000200000000')
});
preimage = getPreimage(tx, game.lockingScript, inputSatoshis);

});
sig = signTx(tx, privateKey, game.lockingScript, inputSatoshis)

const context = { tx, inputIndex, inputSatoshis }

it('n = 8', () => {
run(8, {
is_alice_turn: true,
board: new Bytes('010100000200000002')
});
});
result = game.move(n, new Sig(toHex(sig)), 10000, preimage).verify(context)

expect(result.success, result.error).to.be.true;
}

it('n = 2', () => {
function testMoveNobodyWin(isAliceTurn, n, outputScript0, outputScript1) {
const privateKey = isAliceTurn ? privateKeyAlice : privateKeyBob;

const tx = newTx();

tx.addOutput(new bsv.Transaction.Output({
script: bsv.Script.buildPublicKeyHashOut(privateKey1.toAddress()).toHex(),
script: outputScript0,
satoshis: 10000
}))

tx.addOutput(new bsv.Transaction.Output({
script: outputScript1,
satoshis: 10000
}))


preimage = getPreimage(tx, game.lockingScript, inputSatoshis);

sig = signTx(tx, privateKey1, game.lockingScript, inputSatoshis)
sig = signTx(tx, privateKey, game.lockingScript, inputSatoshis)

const context = { tx, inputIndex, inputSatoshis }

result = game.move(2, new Sig(toHex(sig)), 10000, preimage).verify(context)
result = game.move(n, new Sig(toHex(sig)), 10000, preimage).verify(context)
expect(result.success, result.error).to.be.true;
}

it('One full round where Alice wins', () => {


// Alice places an X at 0-th cell
testMove(true, 0, moveScript(false, [1,0,0,0,0,0,0,0,0]))

// Bob places an O at 4-th cell
testMove(false, 4, moveScript(true, [1,0,0,0,2,0,0,0,0]))


// Alice places an X at 1-th cell
testMove(true, 1, moveScript(false, [1,1,0,0,2,0,0,0,0]))

// Bob places an O at 8-th cell
testMove(false, 8, moveScript(true, [1,1,0,0,2,0,0,0,2]))

// Alice places an X at 2-th cell and wins
testMoveWin(true, 2, bsv.Script.buildPublicKeyHashOut(privateKeyAlice.toAddress()));
});


it('One full round where nobody wins', () => {

reset();
// Alice places an X at 0-th cell
testMove(true, 0, moveScript(false, [1,0,0,0,0,0,0,0,0]))

// Bob places an O at 2-th cell
testMove(false, 2, moveScript(true, [1,0,2,0,0,0,0,0,0]))

// Alice places an X at 1-th cell
testMove(true, 1, moveScript(false, [1,1,2,0,0,0,0,0,0]))

// // Bob places an O at 3-th cell
testMove(false, 3, moveScript(true, [1,1,2,2,0,0,0,0,0]))


// // Alice places an X at 5-th cell
testMove(true, 5, moveScript(false, [1,1,2,2,0,1,0,0,0]))

// // Bob places an O at 4-th cell
testMove(false, 4, moveScript(true, [1,1,2,2,2,1,0,0,0]))


// // Alice places an X at 6-th cell
testMove(true, 6, moveScript(false, [1,1,2,2,2,1,1,0,0]))


// // Bob places an O at 8-th cell
testMove(false, 8, moveScript(true, [1,1,2,2,2,1,1,0,2]))


// // Alice places an X at 7-th cell and nobody wins
testMoveNobodyWin(true, 7, bsv.Script.buildPublicKeyHashOut(privateKeyAlice.toAddress()), bsv.Script.buildPublicKeyHashOut(privateKeyBob.toAddress()));
});


it('should fail if it\'s not alice turn', () => {
// Alice places an X at 0-th cell
reset();
testMove(true, 0, moveScript(false, [1,0,0,0,0,0,0,0,0]))

// Alice places an X at 1-th cell
testMove(true, 1, moveScript(true, [1,1,0,0,0,0,0,0,0]), false)
})

it('should fail if it exceeds the board', () => {
reset();
// Alice places an X at 0-th cell
testMove(true, 0, moveScript(false, [1,0,0,0,0,0,0,0,0]))

// Bob places an O exceeds the board
testMove(true, 11, moveScript(true, [1,0,0,0,0,0,0,0,0]), false)
})


});

0 comments on commit a009119

Please sign in to comment.