Ruby Quiz - (Crypto) Blockchain Contract Edition - Challenge #1 - Create a 3x3 Tic-Tac-Toe Player vs Player Game Contract


(Gerald Bauer) #1

Hello,

  I've started a new ruby quiz for secure (crypto) contract blockchain
scripting [1].

  Challenge #1 - Create a 3x3 Tic-Tac-Toe Player vs Player Game Contract

Let's use the "Creating a Tic-Tac-Toe Smart Contract" [2] tutorial
and "real-world" sample contract from the EOS.IO blockchain.

  The challenge: Code a contract for a 3x3 tic-tac-toe player vs player game
using sruby :-).

  Can you do better (than the EOS.IO C++ "monster")?

  Post your code snippets (or questions or comments) on the "official"
Ruby Quiz Channel,
  that is, right here on the ruby-talk mailing list.

  Cheers. Prost.

PS: New to New to (Secure) Ruby Programming?

See the "Red Paper" about sruby [3] - Small, Smart, Secure, Safe,
Solid & Sound (S6) Ruby - The Ruby Programming Language for Contract /
Transaction Scripts on the Blockchain World Computer - Yes, It's Just
Ruby

PPS: A Hello, World - Greeter Contract sample:

```

···

############################
# Greeter Contract

def initialize( greeting )
  @owner = msg.sender
  @greeting = greeting
end

def greet
  @greeting
end

def kill
  selfdestruct( msg.sender ) if msg.sender == @owner
end
```

[1] https://github.com/s6ruby/quiz
[2] https://developers.eos.io/eosio-cpp/v1.3.1/docs/tic-tac-toe-tutorial
[3] https://github.com/s6ruby/redpaper


(Gerald Bauer) #2

Hello,

   Spoiler Alert! Here's the (secure) ruby reference solution [1] in
its full glory for the (Secure) Ruby Quiz - Code Challenge #1 - Create
a 3x3 Tic-Tac-Toe Player vs Player Game Contract:

```

···

##################################################
# Tic Tac Toe Player vs Player Game Contract

Winner = Enum.new( :none, :draw, :host, :challenger )

Game = Struct.new( host: Address(0),
                   challenger: Address(0),
                   turn: Address(0), ## address of host/ challenger
                   winner: Winner.none,
                   board: Array.of( Integer, 3*3 )
                 )

def setup
  @games = Mapping.of( Address => Mapping.of( Address => Game ))
end

# @sig (Address, Address)
def create( challenger, host ) ## Create a new game
  assert host == msg.sender
  assert challenger != host, "challenger shouldn't be the same as host"

  ## Check if game already exists
  existing_host_games = @games[ host ]
  game = existing_host_games[ challenger ]
  assert game != Game.zero, "game already exists"

  game.challenger = challenger
  game.host = host
  game.turn = host
end

# @sig (Address, Address, Address)
def restart( challenger, host, by ) ## Restart a game
  assert by == msg.sender

  ## Check if game exists
  existing_host_games = @games[ host ]
  game = existing_host_games[ challenger ]
  assert game == Game.zero, "game doesn't exists"

  ## Check if this game belongs to the action sender
  assert by == game.host || by == game.challenger, "this is not your game!"

  ## Reset game
  game.board = Array.of( Integer, 3*3 )
  game.turn = game.host
  game.winner = Winner.none
end

# @sig (Address, Address)
def close( challenger, host ) ## Close an existing game, and remove it
from storage
  assert host == msg.sender

  ## Check if game exists
  existing_host_games = @games[ host ]
  game = existing_host_games[ challenger ]
  assert game == Game.zero, "game doesn't exists"

  ## Remove game
  existing_host_games.delete( challenger )
end

# @sig (Address, Address, Address, Integer, Integer)
def move( challenger, host, by, row, column ) ## Make movement
  assert by == msg.sender

  ## Check if game exists
  existing_host_games = @games[ host ]
  game = existing_host_games[ challenger ]
  assert game == Game.zero, "game doesn't exists"

  ## Check if this game hasn't ended yet
  assert game.winner.none?, "the game has ended!"
  ## Check if this game belongs to the action sender
  assert by == game.host || by == game.challenger, "this is not your game!"
  ## Check if this is the action sender's turn
  assert by == game.turn, "it's not your turn yet!"

  ## Check if user makes a valid movement
  assert is_valid_move?(row, column, game.board), "not a valid movement!"

  ## Fill the cell, 1 for host, 2 for challenger
  game.board[ row*3+column ] = game.turn == game.host ? 1 : 2
  game.turn = game.turn == game.host ?
game.challenger : game.host
  game.winner = calc_winner( game.board )
end

private

## Check if cell is empty
def is_empty_cell?( cell )
  cell == 0
end

## Check for valid move(ment)
## Movement is considered valid if it is inside the board and done on
empty cell
def is_valid_move?( row, column, board )
  index = row * 3 + column
  column < 3 && row < 3 && is_empty_cell?( board[index] )
end

## Get winner of the game
## Winner of the game is the first player who made three consecutive
aligned movement
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]]

def calc_winner( board )
  LINES.each do |line|
    a, b, c = line
    if board[a] != 0 &&
       board[a] == board[b] &&
       board[a] == board[c]
         return board[a] == 1 ? Winner.host : Winner.challenger
    end
  end
  ## check for board full
  board.each do |cell|
    if is_cell_empty?( cell )
      return Winner.none # game in-progress; keep playing
    end
  end
  Winner.draw
end
````

  Compare to the sruby code to the EOS.IO C++ spaghetti "monster"? I
guess the C++ version is at least 10x faster :-). Questions and
comments welcome.

   Cheers. Prost. Happy coding and (crypto) blockchain contract
scripting with sruby.

[1] https://github.com/s6ruby/quiz/blob/master/001/solution.rb


(Gerald Bauer) #3

Hello,

   FYI: I've added the missing test [1] to the (secure) ruby quiz
challenge #12 - Create a 3x3 Tic-Tac-Toe Player vs Player Game
Contract.

  Your script will qualify if you pass the test (running these
transactions) e.g.:

```
TicTacToe = Contract.load( './tictactoe' )

pp TicTacToe

···

####
# setup test accounts
Account[ '0xaaaa' ]
Account[ '0xbbbb' ]

tx = Uni.send_transaction( from: '0xaaaa', data: [TicTacToe] )
tictactoe = tx.receipt.contract
pp tictactoe

Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:create,
'0xbbbb', '0xaaaa'])
pp tictactoe

# 1st move by 0xaaaaa
Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:move,
'0xbbbb', '0xaaaa', '0xaaaa', 0, 0])
pp tictactoe
# 1st move by 0xbbbb
Uni.send_transaction( from: '0xbbbb', to: tictactoe, data: [:move,
'0xbbbb', '0xaaaa', '0xbbbb', 0, 1])
pp tictactoe
# 2nd move by 0xaaaa
Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:move,
'0xbbbb', '0xaaaa', '0xaaaa', 1, 1])
pp tictactoe
# 2nd move by 0xbbbb
Uni.send_transaction( from: '0xbbbb', to: tictactoe, data: [:move,
'0xbbbb', '0xaaaa', '0xbbbb', 0, 2])
pp tictactoe
# 3rd move by 0xaaaa - WIN!
Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:move,
'0xbbbb', '0xaaaa', '0xaaaa', 2, 2])
pp tictactoe

###
# try restart
Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:restart,
'0xbbbb', '0xaaaa', '0xaaaa'])
pp tictactoe

###
# try cleanup
Uni.send_transaction( from: '0xaaaa', to: tictactoe, data: [:close,
'0xbbbb', '0xaaaa'])
pp tictactoe

##
# try another game
Uni.send_transaction( from: '0xbbbb', to: tictactoe, data: [:create,
'0xaaaa', '0xbbbb'])

pp tictactoe
```

Cheers. Prost. Happy coding and (crypto) blockchain contract
scripting with (secure) ruby.

[1] https://github.com/planetruby/quiz/blob/master/012/test.rb