├── .gitignore ├── AlgoNim_presentation.pdf ├── LICENSE ├── README.md ├── algonim.py ├── algonim_asa.py ├── algonim_asc1.py ├── algonim_lib.py └── algonim_moves.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Ignore match files 132 | algonim.match 133 | algonim_asc1_*.teal 134 | algonim_asc1_*.tealc 135 | -------------------------------------------------------------------------------- /AlgoNim_presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cusma/algonim/089a13fa3a6c61768fc05cfc36ee0bc4f4f539c1/AlgoNim_presentation.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cosimo Bassi (aka cusma) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | _ __ ____ _____ _ 3 | / \ [ | |_ \|_ _| (_) 4 | / _ \ | | .--./) .--. | \ | | __ _ .--..--. 5 | / ___ \ | | / /'`\;/ .'`\ \ | |\ \| | [ | [ `.-. .-. | 6 | _/ / \ \_ | | \ \._//| \__. |_| |_\ |_ | | | | | | | | 7 | |____| |____|[___].',__` '.__.'|_____|\____|[___][___||__||__] 8 | ( ( __)) 9 | by cusma 10 | ``` 11 | # AlgoNim: let's play a crypto-Nim on Algorand from the CLI 12 | 13 | ## What's Nim? 14 | [**Nim**](https://en.wikipedia.org/wiki/Nim) is a very simple mathematical game of strategy for two players. With a lot of imagination let's name them **Alice** and **Bob**. 15 | 16 | Just to be fair from the very beginning: Nim is a **zero-sum game** and has been **"mathematically solved"**, this means that exists an **"easily calculated"** perfect strategy to determine which player will win and what winning moves are open to that player. 17 | 18 | **So if Alice is a computer, Bob better avoid betting on winning the game.** 19 | 20 | ## What's AlgoNim? 21 | **AlgoNim** is a cryptographic version of Nim that runs on [Algorand](https://algorand.foundation/) Layer 1, directly on the Pure Proof of Stake consensus protocol, so nobody can cheat. The game implementation takes advantage of all the features introduced in Algorand 2.0 protocol: [**Algorand Standard Assets (ASA)**](https://developer.algorand.org/docs/features/asa/), [**Atomic Transfers (AT)**](https://developer.algorand.org/docs/features/atomic_transfers/) and [**Algorand Smart Contracts (ASC1)**](https://developer.algorand.org/docs/features/asc1/) using Algorand [**Python SDK**](https://developer.algorand.org/docs/reference/sdks/#python) + [**PyTeal**](https://github.com/algorand/pyteal). PyTeal is a binding for [**TEAL**](https://developer.algorand.org/docs/features/asc1/teal_overview/), the **stateless bytecode stack based** language for ASC1, in this sense AlgoNim is a truly stateless game. 22 | 23 | Through the **seamless interaction** between Algorand Python SDK and PyTeal, AlgoNim **automatically writes** and initializes a **dedicated set of stateless TEAL ASC1s and ASAs** for each match. The whole match set-up **takes few seconds** and **costs about 0.008 ALGOS** for transactions fees. AlgoNim accounts initialization and opt-in **require minimum balances**, so the Dealer needs 0.8 ALGO that can be refunded by slightly enhancing the TEAL ASCs1 making them more cost-efficient. Considerng that a new ASA + ASC1 architecture is generated for each match, **this time/cost performance is quite impressive if compared to other blockchains**. 24 | 25 | AlgoNim is played entirely from the **command line interface**. Find other AlgoNim players: https://t.me/algonim 26 | 27 | ## AlgoNim rules 28 | AlgoNim is based on **Nim's "normal" single heap variant**. Alice is the player who creates the match: she is the **Dealer** and sets up the game table. Bob is the **Opponent**. 29 | 30 | Rules are trivial: 31 | 1. The Dealer chooses a heap of **N** pieces to be palced on the game table for the match; 32 | 2. The Dealer chooses the number **M** of pieces that can be removed at the most from the game table in each turn; 33 | 3. Alice and Bob choose who moves first; 34 | 4. On each turn each player removes **at least 1** and **at the most M** pieces from the game table; 35 | 36 | **Who removes the last piece of the heap form the table wins the match!** 37 | 38 | Alice and Bob may choose **betting** some ALGOs for the match. Further implementations will accept **AlgoNim ASA Score Points** other then the betting reward for the matches, this will enable an **AlgoNim global ranking** too! 39 | 40 | ## Install AlgoNim 41 | ### Step 1 - Python modules 42 | AlgoNim uses the following Python3 modules: 43 | 1. `msgpack` 44 | 2. `docopt` 45 | 3. `algosdk` 46 | 4. `pyteal` 47 | 48 | so you need to install them (if not already present): 49 | 50 | ```bash 51 | $ pip3 install --upgrade msgpack 52 | $ pip3 install --upgrade docopt 53 | $ pip3 install --upgrade py-algorand-sdk 54 | $ pip3 install --upgrade pyteal 55 | ``` 56 | 57 | ### Step 2 - Environment setting 58 | To run AlgoNim smoothly you need to set the following environmental variables: 59 | ```bash 60 | $ export ALGORAND_DATA=/path/to/node/data 61 | $ export PATH=/path/to/node/:$PATH 62 | ``` 63 | Attention: setting `$ALGORAND_DATA` on your node you choose playing AlgoNim on MainNet, TestNet or BetaNet. 64 | 65 | ### Step 3 - AlgoNim files 66 | Copy following AlgoNim files into your `node` directory (the same of `goal`): 67 | 68 | 1. `algonim.py` 69 | 2. `algonim_asa.py` 70 | 3. `algonim_asc1.py` 71 | 4. `algonim_moves.py` 72 | 5. `algonim_lib.py` 73 | 74 | ## How to play 75 | Playing AlgoNim from your CLI is pretty easy, just ask for help: 76 | 77 | **Input** 78 | ```bash 79 | $ python3 algonim.py --help 80 | ``` 81 | **Output** 82 | ``` 83 | AlgoNim, the first crypto-mini-game on Algorand! (by cusma) 84 | Usage: 85 | algonim.py setup 86 | [--bet-amount=] [--pieces=] [--max-removal=] 87 | algonim.py join 88 | algonim.py play 89 | algonim.py status 90 | algonim.py close 91 | algonim.py [--help] 92 | 93 | Commands: 94 | setup Dealer sets up a new AlgoNim match. 95 | join Opponent joins the match. 96 | play Play your turn. 97 | status Display current match status. 98 | close Close expired AlgoNim Bet Escrows. 99 | 100 | Options: 101 | -b --bet-amount= Set the bet amount in microAlgos 102 | [default: 0]. 103 | -p --pieces= Set the total amount of pieces on game table 104 | [default: 21]. 105 | -m --max-removal= Set maximum amount of pieces removal 106 | [default: 4]. 107 | -h --help 108 | ``` 109 | 110 | ### Step 1 - Match set up (Dealer) 111 | In the first step the Dealer sets up the match, generating the ASAs + ASC1s game architecture. To set up the match the Dealer may choose the following options (or left them void for default values otherwise): 112 | 1. `[--bet-amount=]` is the bet proposal expressed in microALGO; 113 | 2. `[--pieces=]` is the number of pieces that the Dealer distributes on the Game Table; 114 | 3. `[--max-removal=]` is the maximum number of pieces that can be removed from the Game Table on each turn by the players; 115 | 116 | **Input** 117 | ```bash 118 | $ python3 algonim.py setup NMZRQMXXYSRKVG4ZYJ5OUIN3AOLWJ2ZB5GVIGECAYM6G77D23MPA4BRP6I 2 20000000 21 4 119 | ``` 120 | **Output** 121 | ``` 122 | _ _ 123 | /\/\ __ _| |_ ___| |__ _ 124 | / \ / _` | __/ __| '_ \ (_) 125 | / /\/\ \ (_| | || (__| | | | _ 126 | \/ \/\__,_|\__\___|_| |_| (_) 127 | 128 | MATCH DURATION: 120.0 min 129 | PIECES ON GAME TABLE: 21 130 | 131 | RULES: 132 | 1. Players on each turn must remove at least 1 ASA Piece 133 | 2. Players on each turn must remove at most 4 ASA Piece 134 | 3. Who removes the last ASA Piece form the Game Table wins the match! 135 | 136 | Player 1 - Dealer: SVMHAG6PLL27YYGQX4ETEIZ2GHLSO6M5ICU2MBJVKMDT2ERPNSE27OGWIE 137 | Player 2 - Opponent: NMZRQMXXYSRKVG4ZYJ5OUIN3AOLWJ2ZB5GVIGECAYM6G77D23MPA4BRP6I 138 | 139 | AlgoNim ASA Pieces ID: 7329523 140 | AlgoNim ASA Turn ID: 7329527 141 | 142 | AlgoNim Sink Account: 7EUFKLR636O34XW2ZRMTVOCQAXIHUDEEKIY4ZPWAGDRU6A5AONKVN5K4R4 143 | AlgoNim Game Table Account: JBASDWK7MQNRCYUDZBBGR4DFHEGQTCSZQWNUMW4O2XBNON5CFLWALGKJCA 144 | AlgoNim Bet Escrow Player 1 Account: PUEKG6EPXF2HMUHB3GTTODXBGUXZX26YK36SJHU7X3ZPQWSKZXUZJAZT3Q 145 | AlgoNim Bet Escrow Player 2 Account: W6YG5653UWDU4XTSK2767FHLQOTLXGRG53ZJGV6SEVTSMWOMJOAAMBGTX4 146 | 147 | Send 'algonim.match' file to your opponent join the match! 148 | 149 | May the best win! 150 | 151 | ``` 152 | The scripts generates both the `*.teal` and `*.tealc` ASC1s files and the `algonim.match` in which match's data are packed. The Dealer than sends `algonim.match` to the Opponent. 153 | 154 | ### Step 2 - Join the match (Opponent) 155 | To join the match the Opponent must decide whether accept the Dealer bet proposal or not. Accepting the proposal the Opponet will Opt-In the match's ASAs and fund both the Bet Escrows with the same amount issuing an Atomic Transfer (already signed by the Dealer). 156 | 157 | **Input** 158 | ```bash 159 | $ python3 algonim.py join 160 | ``` 161 | **Output** 162 | ``` 163 | _ __ ____ _____ _ 164 | / \ [ | |_ \|_ _| (_) 165 | / _ \ | | .--./) .--. | \ | | __ _ .--..--. 166 | / ___ \ | | / /'`\;/ .'`\ \ | |\ \| | [ | [ `.-. .-. | 167 | _/ / \ \_ | | \ \._//| \__. |_| |_\ |_ | | | | | | | | 168 | |____| |____|[___].',__` '.__.'|_____|\____|[___][___||__||__] 169 | ( ( __)) 170 | by cusma 171 | 172 | Welcome to AlgoNim, the first crypto-mini-game on Algorand! 173 | 174 | The Dealer wants to bet 20.0 ALGO. 175 | Do you want to join the match? [y/N] 176 | ``` 177 | Match's ASAs Opt-In and betting AT. 178 | 179 | ### Step 3 - Play turn (Dealer or Opponent) 180 | To play a turn the Player must own the AlgoNim ASA Turn. With `algonim.py play` players may play both a regular turn and the last turn, closing the match and claiming the rewards locked in the Bet Escrows Account. 181 | 182 | **Input** 183 | ```bash 184 | $ python3 algonim.py play 4 185 | ``` 186 | **Output** 187 | ``` 188 | Removing 4 pieces from the Game table... 189 | ``` 190 | Play Turn Atomic Transfer consists of: 191 | 1. Asset Send of 1 ASA Turn to the other player; 192 | 2. Asset Send of an amount **P** (1 <= P <= M) ASA Pieces from the Game Table Account to Sink Account; 193 | 194 | OR 195 | 196 | Play Last Turn Atomic Transfer consists of: 197 | 1. Asset Send of 1 ASA Turn to the other player; 198 | 2. Asset Send of an amount **P** (1 <= P <= M) ASA Pieces from the Game Table Account to Sink Account; 199 | 3. Asset Send of ASA Pieces **total supply** from Sink Account to winner account; 200 | 4. Close Bet Escrow Accounts claiming the betting rewards; 201 | 202 | ### AlgoNim match's status 203 | Each player can check the current match's status with `algonim.py status`: 204 | 205 | **Input** 206 | ```bash 207 | $ python3 algonim.py status NMZRQMXXYSRKVG4ZYJ5OUIN3AOLWJ2ZB5GVIGECAYM6G77D23MPA4BRP6I 208 | ``` 209 | **Output** 210 | ``` 211 | MATCH TOTAL PIECES: 21 212 | PIECES ON THE GAME TABLE: 17 213 | It's your turn! Play your best move! 214 | 215 | OPPONENT BET ESCROW AMOUNT: 20100000 216 | YOUR BET ESCROW AMOUNT: 20100000 217 | Your Bet Escrow is still locked. 82 blocks left! 218 | ``` 219 | Displays ASA Pieces total amount for this match, ASA Pieces currently on the Game Table, Player's Turn and Bet Escrows status. 220 | 221 | ### Bet Escrows closing 222 | At the end of the match **the winner can claim its own bet amount back** closing the Bet Escrow when it expires with `algonim.py close`: 223 | 224 | **Input** 225 | ```bash 226 | $ python3 algonim.py close NMZRQMXXYSRKVG4ZYJ5OUIN3AOLWJ2ZB5GVIGECAYM6G77D23MPA4BRP6I 227 | ``` 228 | 229 | If one of the players does not act for long time, both players can close their expired Bet Escrows claiming their own bets back. 230 | 231 | ## Open future implementations 232 | 1. Improving robustness of Bet Escrows (preventing players to stop the game in the middle simply waiting Bet Escrows expiry); 233 | 2. Freezing match’s ASAs for anyone but the players; 234 | 3. Automatically destroying match’s ASAs at the end of the game; 235 | 4. Adding ASA AlgoNim Score in the Sink from Scores Pool as reward for the winner; 236 | 5. Implementing a "Multi-heaps" variant; 237 | 6. Implementing a "Championship" mode (2 out of 3 matches). 238 | 239 | ## Troubleshooting 240 | 241 | ### Issue with `KeyError: 'microalgo_bet_amount'` 242 | 243 | This issue arises if you do not use the latest version of `msgpack`. 244 | `msgpack` version 1.0.0 is needed. 245 | Run: 246 | ```bash 247 | $ pip3 install --upgrade msgpack 248 | ``` 249 | 250 | ## Contact 251 | For any issue, improvement proposal or comment please reach me out at: algonim.cusma@gmail.com 252 | 253 | ## Tip the Dev 254 | 255 | If you enjoyed AlgoNim or find it useful as free and open source learning example, consider tipping the Dev: 256 | 257 | `XODGWLOMKUPTGL3ZV53H3GZZWMCTJVQ5B2BZICFD3STSLA2LPSH6V6RW3I` 258 | -------------------------------------------------------------------------------- /algonim.py: -------------------------------------------------------------------------------- 1 | """AlgoNim, the first crypto-mini-game on Algorand! (by cusma) 2 | Usage: 3 | algonim.py setup 4 | [--bet-amount=] [--pieces=] [--max-removal=] 5 | algonim.py join 6 | algonim.py play 7 | algonim.py status 8 | algonim.py close 9 | algonim.py [--help] 10 | 11 | Commands: 12 | setup Dealer sets up a new AlgoNim match. 13 | join Opponent joins the match. 14 | play Play your turn. 15 | status Display current match status. 16 | close Close expired AlgoNim Bet Escrows. 17 | 18 | Options: 19 | -b --bet-amount= Set the bet amount in microAlgos 20 | [default: 0]. 21 | -p --pieces= Set the total amount of pieces on game table 22 | [default: 21]. 23 | -m --max-removal= Set maximum amount of pieces removal 24 | [default: 4]. 25 | -h --help 26 | """ 27 | 28 | import sys 29 | from docopt import docopt 30 | from algonim_moves import * 31 | 32 | 33 | def main(): 34 | if len(sys.argv) == 1: 35 | # Display help if no arguments, see: 36 | # https://github.com/docopt/docopt/issues/420#issuecomment-405018014 37 | sys.argv.append('--help') 38 | 39 | args = docopt(__doc__) 40 | 41 | algod_client = create_algod_client() 42 | try: 43 | algod_client.compile( 44 | compileTeal(Global.group_size() == Int(1), Mode.Signature)) 45 | except Exception: 46 | raise ErrAlgodClientCompile 47 | 48 | if args['setup']: 49 | dealer_passphrase = args[''] 50 | addr_opponent = args[''] 51 | match_hours_timeout = float(args['']) 52 | microalgo_bet_amount = int(args['--bet-amount']) 53 | asa_pieces_total = int(args['--pieces']) 54 | asa_pieces_max_remove = int(args['--max-removal']) 55 | assert microalgo_bet_amount >= 0 56 | assert asa_pieces_total > asa_pieces_max_remove + 1 57 | match_setup(algod_client, dealer_passphrase, addr_opponent, 58 | match_hours_timeout, microalgo_bet_amount, 59 | asa_pieces_total, asa_pieces_max_remove) 60 | 61 | elif args['join']: 62 | with open("algonim.match", "rb") as f: 63 | match_data_bytes = f.read() 64 | f.close() 65 | match_data = msgpack.unpackb(match_data_bytes) 66 | 67 | opponent_private_key = mnemonic.to_private_key(args['']) 68 | opponent = {'pk': address_from_private_key(opponent_private_key), 69 | 'sk': opponent_private_key} 70 | microalgo_bet_amount = match_data['microalgo_bet_amount'] 71 | 72 | print( 73 | r""" 74 | 75 | _ __ ____ _____ _ 76 | / \ [ | |_ \|_ _| (_) 77 | / _ \ | | .--./) .--. | \ | | __ _ .--..--. 78 | / ___ \ | | / /'`\;/ .'`\ \ | |\ \| | [ | [ `.-. .-. | 79 | _/ / \ \_ | | \ \._//| \__. |_| |_\ |_ | | | | | | | | 80 | |____| |____|[___].',__` '.__.'|_____|\____|[___][___||__||__] 81 | ( ( __)) 82 | by cusma 83 | 84 | Welcome to AlgoNim, the first crypto-mini-game on Algorand! 85 | """ 86 | ) 87 | 88 | print("") 89 | print("The Dealer wants to bet", microalgo_bet_amount * 10 ** -6, 90 | "ALGO.") 91 | join = input("Do you want to join the match? [y/N]") 92 | if join == 'y': 93 | # AlgoNim ASAs Opt-In 94 | asa_pieces_opt_in_txn = unsigned_asset_send( 95 | algod_client, opponent['pk'], opponent['pk'], 96 | match_data['asa_pieces_id'], 0) 97 | 98 | asa_pieces_opt_in_stxn = asa_pieces_opt_in_txn.sign(opponent['sk']) 99 | 100 | txid = algod_client.send_transactions([asa_pieces_opt_in_stxn]) 101 | print("\nAlgoNim ASA Piece Opt-In...") 102 | print("Transaction ID:", txid) 103 | wait_for_tx_confirmation(algod_client, txid) 104 | 105 | asa_turn_opt_in_txn = unsigned_asset_send( 106 | algod_client, opponent['pk'], opponent['pk'], 107 | match_data['asa_turn_id'], 0) 108 | 109 | asa_turn_opt_in_stxn = asa_turn_opt_in_txn.sign(opponent['sk']) 110 | 111 | txid = algod_client.send_transactions([asa_turn_opt_in_stxn]) 112 | print("\nAlgoNim ASA Turn Opt-In...") 113 | print("Transaction ID:", txid) 114 | wait_for_tx_confirmation(algod_client, txid) 115 | 116 | # AlgoNim Players' Bet Atomic Transfer 117 | dealer_bet_stxn = encoding.msgpack_decode( 118 | match_data['dealer_bet_stxn']) 119 | opponent_bet_txn = encoding.msgpack_decode( 120 | match_data['opponent_bet_txn']) 121 | opponent_bet_stxn = opponent_bet_txn.sign(opponent['sk']) 122 | bet_sgtxn = [dealer_bet_stxn, opponent_bet_stxn] 123 | txid = algod_client.send_transactions(bet_sgtxn) 124 | print("\nPlayers betting...") 125 | print("Transaction ID: ", txid) 126 | wait_for_tx_confirmation(algod_client, txid) 127 | 128 | dealer_info = algod_client.account_info(match_data['dealer']) 129 | asa_pieces_total = next( 130 | (asa['params']['total'] for asa in 131 | dealer_info['created-assets'] if 132 | asa['index'] == match_data['asa_pieces_id']), None) 133 | 134 | print( 135 | r""" 136 | 137 | _ _ 138 | /\/\ __ _| |_ ___| |__ _ 139 | / \ / _` | __/ __| '_ \ (_) 140 | / /\/\ \ (_| | || (__| | | | _ 141 | \/ \/\__,_|\__\___|_| |_| (_) 142 | """ 143 | ) 144 | print("MATCH DURATION:\t\t", 145 | match_data['match_hours_timeout'] * 60, "min") 146 | print("PIECES ON GAME TABLE:\t", asa_pieces_total, "\n") 147 | print("RULES:") 148 | print("1. Players on each turn must remove at least 1 ASA Piece") 149 | print("2. Players on each turn must remove at most", 150 | match_data['asa_pieces_max_remove'], "ASA Piece") 151 | print("3. Who removes the last ASA Piece form the Game Table wins " 152 | + "the match!\n") 153 | print("Player 1 - Dealer:\t" + match_data['dealer']) 154 | print("Player 2 - Opponent:\t" + match_data['opponent'], "\n") 155 | print("AlgoNim ASA Pieces ID:\t", match_data['asa_pieces_id']) 156 | print("AlgoNim ASA Turn ID:\t", match_data['asa_turn_id'], "\n") 157 | print("AlgoNim Sink Account:\t\t\t" + match_data['sink']) 158 | print("AlgoNim Game Table Account:\t\t" + match_data['game_table']) 159 | print("AlgoNim Bet Escrow Player 1 Account:\t" 160 | + match_data['dealer_bet_escrow']) 161 | print("AlgoNim Bet Escrow Player 2 Account:\t" 162 | + match_data['opponent_bet_escrow']) 163 | print("\nMay the best win!\n") 164 | else: 165 | print("See you for the next AlgoNim match! Arrivederci!") 166 | 167 | elif args['play']: 168 | with open("algonim.match", "rb") as f: 169 | match_data_bytes = f.read() 170 | f.close() 171 | match_data = msgpack.unpackb(match_data_bytes) 172 | player_private_key = mnemonic.to_private_key(args['']) 173 | player = {'pk': address_from_private_key(player_private_key), 174 | 'sk': player_private_key} 175 | asa_pieces_amount = int(args['']) 176 | 177 | asa_pieces_id = match_data['asa_pieces_id'] 178 | asa_pieces_max_remove = match_data['asa_pieces_max_remove'] 179 | asa_turn_id = match_data['asa_turn_id'] 180 | addr_sink = match_data['sink'] 181 | sink_lsig = encoding.msgpack_decode(match_data['sink_lsig']) 182 | addr_game_table = match_data['game_table'] 183 | game_table_lsig = encoding.msgpack_decode( 184 | match_data['game_table_lsig']) 185 | addr_dealer_bet_escrow = match_data['dealer_bet_escrow'] 186 | dealer_bet_escrow_lsig = encoding.msgpack_decode( 187 | match_data['dealer_bet_escrow_lsig']) 188 | 189 | dealer_info = algod_client.account_info(match_data['dealer']) 190 | asa_pieces_total = next( 191 | (asa['params']['total'] for asa in dealer_info['created-assets'] if 192 | asa['index'] == match_data['asa_pieces_id']), None) 193 | 194 | if player['pk'] == match_data['dealer']: 195 | addr_adversary = match_data['opponent'] 196 | addr_adversary_bet_escrow = match_data['opponent_bet_escrow'] 197 | adversary_bet_escrow_lsig = encoding.msgpack_decode( 198 | match_data['opponent_bet_escrow_lsig']) 199 | play_turn(algod_client, player, addr_adversary, 200 | addr_game_table, game_table_lsig, 201 | addr_sink, sink_lsig, 202 | addr_adversary_bet_escrow, adversary_bet_escrow_lsig, 203 | asa_turn_id, asa_pieces_id, 204 | asa_pieces_max_remove, asa_pieces_amount, 205 | asa_pieces_total) 206 | else: 207 | addr_adversary = match_data['dealer'] 208 | addr_adversary_bet_escrow = match_data['dealer_bet_escrow'] 209 | adversary_bet_escrow_lsig = encoding.msgpack_decode( 210 | match_data['dealer_bet_escrow_lsig']) 211 | play_turn(algod_client, player, addr_adversary, 212 | addr_game_table, game_table_lsig, 213 | addr_sink, sink_lsig, 214 | addr_adversary_bet_escrow, adversary_bet_escrow_lsig, 215 | asa_turn_id, asa_pieces_id, 216 | asa_pieces_max_remove, asa_pieces_amount, 217 | asa_pieces_total) 218 | 219 | elif args['status']: 220 | with open("algonim.match", "rb") as f: 221 | match_data_bytes = f.read() 222 | f.close() 223 | match_data = msgpack.unpackb(match_data_bytes) 224 | player_info = algod_client.account_info(args['']) 225 | game_table_info = algod_client.account_info(match_data['game_table']) 226 | dealer_info = algod_client.account_info(match_data['dealer']) 227 | asa_pieces_total = next( 228 | (asa['params']['total'] for asa in dealer_info['created-assets'] if 229 | asa['index'] == match_data['asa_pieces_id']), None) 230 | pieces_on_table = next( 231 | (asa['amount'] for asa in game_table_info['assets'] if 232 | asa['asset-id'] == match_data['asa_pieces_id']), None) 233 | 234 | dealer_bet_escrow_info = algod_client.account_info( 235 | match_data['dealer_bet_escrow']) 236 | opponent_bet_escrow_info = algod_client.account_info( 237 | match_data['opponent_bet_escrow']) 238 | 239 | print("\nMATCH TOTAL PIECES:\t\t" + str(asa_pieces_total)) 240 | print("PIECES ON THE GAME TABLE:\t" + str(pieces_on_table)) 241 | 242 | if pieces_on_table != 0: 243 | if next((asa['amount'] for asa in player_info['assets'] if 244 | asa['asset-id'] == match_data['asa_turn_id']), None) == 1: 245 | print("It's your turn! Play your best move!") 246 | else: 247 | print("Your opponent is playing the turn...") 248 | else: 249 | print("The match is over!") 250 | 251 | dealer_bet_escrow_amount = dealer_bet_escrow_info['amount'] 252 | opponent_bet_escrow_amount = opponent_bet_escrow_info['amount'] 253 | 254 | if args[''] == match_data['dealer']: 255 | print("\nOPPONENT BET ESCROW AMOUNT:\t", 256 | opponent_bet_escrow_amount) 257 | print("YOUR BET ESCROW AMOUNT:\t\t", 258 | dealer_bet_escrow_amount) 259 | if dealer_bet_escrow_amount != 0: 260 | blockchain_params = algod_client.suggested_params() 261 | last_round = blockchain_params.first 262 | bet_escrow_expiry = match_data[ 263 | 'dealer_bet_escrow_expiry'] - last_round 264 | if bet_escrow_expiry > 0: 265 | print("Your Bet Escrow is still locked.", 266 | bet_escrow_expiry, "blocks left!\n") 267 | else: 268 | print("Your Bet Escrow expired! " + 269 | "You can claim your bet back.") 270 | elif args[''] == match_data['opponent']: 271 | print("\nOPPONENT BET ESCROW AMOUNT:\t", 272 | dealer_bet_escrow_amount) 273 | print("YOUR BET ESCROW AMOUNT:\t\t", 274 | opponent_bet_escrow_amount) 275 | if opponent_bet_escrow_amount != 0: 276 | blockchain_params = algod_client.suggested_params() 277 | last_round = blockchain_params.first 278 | bet_escrow_expiry = match_data[ 279 | 'opponent_bet_escrow_expiry'] - last_round 280 | if bet_escrow_expiry > 0 and opponent_bet_escrow_amount != 0: 281 | print("Your Bet Escrow is still locked.", 282 | bet_escrow_expiry, "blocks left!\n") 283 | else: 284 | print("Your Bet Escrow expired! " + 285 | "You can claim your bet back.") 286 | else: 287 | print("Invalid player address for this match!") 288 | 289 | elif args['close']: 290 | with open("algonim.match", "rb") as f: 291 | match_data_bytes = f.read() 292 | f.close() 293 | match_data = msgpack.unpackb(match_data_bytes) 294 | if args[''] == match_data['dealer']: 295 | dealer_bet_escrow_lsig = encoding.msgpack_decode( 296 | match_data['dealer_bet_escrow_lsig']) 297 | close_bet_escrow(algod_client, 298 | match_data['dealer_bet_escrow'], 299 | match_data['dealer'], 300 | dealer_bet_escrow_lsig) 301 | elif args[''] == match_data['opponent']: 302 | opponent_bet_escrow_lsig = encoding.msgpack_decode( 303 | match_data['opponent_bet_escrow_lsig']) 304 | close_bet_escrow(algod_client, 305 | match_data['opponent_bet_escrow'], 306 | match_data['opponent'], 307 | opponent_bet_escrow_lsig) 308 | else: 309 | print("Invalid player address for this match!") 310 | 311 | else: 312 | print("\nError: read AlgoNim '--help'!\n") 313 | 314 | 315 | class ErrAlgodClientCompile(Exception): 316 | def __init__(self): 317 | Exception.__init__( 318 | self, "\n\nAlgoNim failed to compile TEAL source code. Please:\n\n" 319 | + "\t1. Go to your Algorand node data folder\n" 320 | + "\t2. Copy 'config.json.example' as 'config.json'\n" 321 | + "\t3. Open and edit 'config.json'\n" 322 | + "\t4. Set \"EnableDeveloperAPI\" to true and save\n" 323 | + "\t5. Restart your Algorand node: goal node restart\n") 324 | 325 | 326 | if __name__ == "__main__": 327 | main() 328 | -------------------------------------------------------------------------------- /algonim_asa.py: -------------------------------------------------------------------------------- 1 | from algosdk import transaction 2 | from algonim_lib import * 3 | 4 | 5 | def asa_pieces_create(algod_client, 6 | asset_creator, 7 | total): 8 | """HELP asa_pieces_create: 9 | (AlgodClient, dict, int) - Returns AlgoNim ASA Pieces Asset ID. 10 | """ 11 | assert type(total) == int 12 | # Get network suggested params for transactions. 13 | params = algod_client.suggested_params() 14 | first_valid = params.first 15 | last_valid = first_valid + 1000 16 | gh = params.gh 17 | min_fee = params.min_fee 18 | assert min_fee <= 1000 19 | 20 | data = { 21 | "sender": asset_creator['pk'], 22 | "fee": min_fee, 23 | "first": first_valid, 24 | "last": last_valid, 25 | "gh": gh, 26 | "total": total, 27 | "default_frozen": False, 28 | "unit_name": 'ALGONIMP', 29 | "asset_name": 'AlgoNim Piece', 30 | "manager": asset_creator['pk'], 31 | "reserve": asset_creator['pk'], 32 | "freeze": '', 33 | "clawback": '', 34 | "url": 'https://github.com/cusma/algonim', 35 | "flat_fee": True, 36 | "strict_empty_address_check": False, 37 | "decimals": 0 38 | } 39 | 40 | txn = transaction.AssetConfigTxn(**data) 41 | stxn = txn.sign(asset_creator['sk']) 42 | txid = algod_client.send_transaction(stxn) 43 | print("Transaction ID:", txid) 44 | wait_for_tx_confirmation(algod_client, txid) 45 | try: 46 | ptx = algod_client.pending_transaction_info(txid) 47 | asa_id = ptx['asset-index'] 48 | print("AlgoNim ASA Piece ID: {}".format(asa_id)) 49 | return asa_id 50 | except Exception as e: 51 | print(e) 52 | 53 | 54 | def asa_turn_create(algod_client, 55 | asset_creator): 56 | """HELP asa_turn_create: 57 | (AlgodClient, dict) - Returns AlgoNim ASA Turn Asset ID. 58 | """ 59 | # Get network suggested params for transactions. 60 | params = algod_client.suggested_params() 61 | first_valid = params.first 62 | last_valid = first_valid + 1000 63 | gh = params.gh 64 | min_fee = params.min_fee 65 | assert min_fee <= 1000 66 | 67 | data = { 68 | "sender": asset_creator['pk'], 69 | "fee": min_fee, 70 | "first": first_valid, 71 | "last": last_valid, 72 | "gh": gh, 73 | "total": 1, 74 | "default_frozen": False, 75 | "unit_name": 'ALGONIMT', 76 | "asset_name": 'AlgoNim Turn', 77 | "manager": asset_creator['pk'], 78 | "reserve": asset_creator['pk'], 79 | "freeze": '', 80 | "clawback": '', 81 | "url": 'https://github.com/cusma/algonim', 82 | "flat_fee": True, 83 | "strict_empty_address_check": False, 84 | "decimals": 0 85 | } 86 | 87 | txn = transaction.AssetConfigTxn(**data) 88 | stxn = txn.sign(asset_creator['sk']) 89 | txid = algod_client.send_transaction(stxn) 90 | print("Transaction ID:", txid) 91 | wait_for_tx_confirmation(algod_client, txid) 92 | try: 93 | ptx = algod_client.pending_transaction_info(txid) 94 | asa_id = ptx['asset-index'] 95 | print("AlgoNim ASA Turn ID: {}".format(asa_id)) 96 | return asa_id 97 | except Exception as e: 98 | print(e) 99 | 100 | 101 | def asa_score_create(algod_client, 102 | asset_creator): 103 | """HELP asa_score_create: 104 | (AlgodClient, dict) - Returns AlgoNim ASA Score Asset ID. 105 | """ 106 | # Get network suggested params for transactions. 107 | params = algod_client.suggested_params() 108 | first_valid = params.first 109 | last_valid = first_valid + 1000 110 | gh = params.gh 111 | min_fee = params.min_fee 112 | assert min_fee <= 1000 113 | 114 | data = { 115 | "sender": asset_creator['pk'], 116 | "fee": min_fee, 117 | "first": first_valid, 118 | "last": last_valid, 119 | "gh": gh, 120 | "total": 1, 121 | "default_frozen": False, 122 | "unit_name": 'ALGONIMS', 123 | "asset_name": 'AlgoNim Score', 124 | "manager": asset_creator['pk'], 125 | "reserve": asset_creator['pk'], 126 | "freeze": '', 127 | "clawback": '', 128 | "url": 'https://github.com/cusma/algonim', 129 | "flat_fee": True, 130 | "strict_empty_address_check": False, 131 | "decimals": 0 132 | } 133 | 134 | txn = transaction.AssetConfigTxn(**data) 135 | stxn = txn.sign(asset_creator['sk']) 136 | txid = algod_client.send_transaction(stxn) 137 | print("Transaction ID:", txid) 138 | wait_for_tx_confirmation(algod_client, txid) 139 | try: 140 | ptx = algod_client.pending_transaction_info(txid) 141 | asa_id = ptx['asset-index'] 142 | print("AlgoNim ASA Score ID: {}".format(asa_id)) 143 | return asa_id 144 | except Exception as e: 145 | print(e) 146 | -------------------------------------------------------------------------------- /algonim_asc1.py: -------------------------------------------------------------------------------- 1 | from pyteal import * 2 | 3 | 4 | def asc1_sink_teal(asa_pieces_total, 5 | asa_pieces_id, 6 | player_alice, 7 | player_bob): 8 | """HELP asc1_sink_teal: 9 | (int, int, str, str) - Returns AlgoNim ASC1 Sink raw TEAL. 10 | """ 11 | # AlgoNim ASC1 Sink controls the following conditions: 12 | # 1. AlgoNim ASA Pieces Opt-In 13 | # 2. Empty Sink: Alice or Bob remove all the AlgoNim ASA Pieces total 14 | # supply from the Sink as winning proof. 15 | 16 | # ASC1 Constants: 17 | tmpl_fee = Int(1000) 18 | 19 | # ASC1 Logic: 20 | # 1. AlgoNim ASA Pieces Opt-In 21 | asa_pieces_opt_in = And(Global.group_size() == Int(1), 22 | Txn.group_index() == Int(0), 23 | Txn.type_enum() == Int(4), 24 | Txn.fee() <= tmpl_fee, 25 | Txn.xfer_asset() == Int(asa_pieces_id), 26 | Txn.asset_amount() == Int(0), 27 | Txn.asset_close_to() == Global.zero_address(), 28 | Txn.rekey_to() == Global.zero_address()) 29 | 30 | # 2. Empty Sink 31 | empty_sink_alice = And(Global.group_size() == Int(4), 32 | Txn.group_index() == Int(2), 33 | Txn.type_enum() == Int(4), 34 | Txn.fee() <= tmpl_fee, 35 | Txn.xfer_asset() == Int(asa_pieces_id), 36 | Txn.asset_amount() == Int(asa_pieces_total), 37 | Txn.asset_receiver() == Addr(player_alice), 38 | Txn.asset_close_to() == Global.zero_address(), 39 | Txn.rekey_to() == Global.zero_address()) 40 | 41 | empty_sink_bob = And(Global.group_size() == Int(4), 42 | Txn.group_index() == Int(2), 43 | Txn.type_enum() == Int(4), 44 | Txn.fee() <= tmpl_fee, 45 | Txn.xfer_asset() == Int(asa_pieces_id), 46 | Txn.asset_amount() == Int(asa_pieces_total), 47 | Txn.asset_receiver() == Addr(player_bob), 48 | Txn.asset_close_to() == Global.zero_address(), 49 | Txn.rekey_to() == Global.zero_address()) 50 | 51 | empty_sink = Or(empty_sink_alice, empty_sink_bob) 52 | asc1_sink = Or(asa_pieces_opt_in, empty_sink) 53 | return compileTeal(asc1_sink, Mode.Signature) 54 | 55 | 56 | def asc1_game_table_teal(asa_pieces_total, 57 | asa_pieces_id, 58 | asa_pieces_max_remove, 59 | asa_turn_id, 60 | player_alice, 61 | player_bob, 62 | asc1_sink): 63 | """HELP asc1_game_table_teal: 64 | (int, int, int, int, int, str, str, str) - Returns AlgoNim ASC1 65 | Game Table raw TEAL 66 | """ 67 | # AlgoNim ASC1 Game Table controls the following conditions: 68 | # 1. Dealer - Funding Game Table with AlgoNim ASA Pieces 69 | # 2. Play Turn - Player correctly removes ASA Pieces from the Game Table 70 | 71 | # ASC1 Constants: 72 | tmpl_fee = Int(1000) 73 | 74 | # ASC1 Logic: 75 | # 1. Dealer 76 | asa_pieces_opt_in = And(Global.group_size() == Int(2), 77 | Txn.group_index() == Int(0), 78 | Gtxn[0].type_enum() == Int(4), 79 | Gtxn[0].fee() <= tmpl_fee, 80 | Gtxn[0].xfer_asset() == Int(asa_pieces_id), 81 | Gtxn[0].asset_amount() == Int(0), 82 | Gtxn[0].asset_close_to() == Global.zero_address(), 83 | Gtxn[0].rekey_to() == Global.zero_address()) 84 | 85 | game_table_setup = And(Global.group_size() == Int(2), 86 | Gtxn[1].type_enum() == Int(4), 87 | Gtxn[1].fee() <= tmpl_fee, 88 | Gtxn[1].xfer_asset() == Int(asa_pieces_id), 89 | Gtxn[1].asset_amount() == Int(asa_pieces_total), 90 | Gtxn[1].sender() == Addr(player_alice), 91 | Gtxn[1].asset_close_to() == Global.zero_address(), 92 | Gtxn[1].rekey_to() == Global.zero_address()) 93 | 94 | dealer = And(asa_pieces_opt_in, game_table_setup) 95 | 96 | # 2. Play Turn 97 | play_turn_type = Or(Global.group_size() == Int(2), 98 | Global.group_size() == Int(4)) 99 | 100 | change_turn_alice_to_bob = And(play_turn_type, 101 | Gtxn[0].type_enum() == Int(4), 102 | Gtxn[0].fee() <= tmpl_fee, 103 | Gtxn[0].xfer_asset() == Int(asa_turn_id), 104 | Gtxn[0].asset_amount() == Int(1), 105 | Gtxn[0].sender() == Addr(player_alice), 106 | Gtxn[0].asset_receiver() == Addr(player_bob), 107 | Gtxn[0].asset_close_to() == Global.zero_address(), 108 | Gtxn[0].rekey_to() == Global.zero_address()) 109 | 110 | change_turn_bob_to_alice = And(play_turn_type, 111 | Gtxn[0].type_enum() == Int(4), 112 | Gtxn[0].fee() <= tmpl_fee, 113 | Gtxn[0].xfer_asset() == Int(asa_turn_id), 114 | Gtxn[0].asset_amount() == Int(1), 115 | Gtxn[0].sender() == Addr(player_bob), 116 | Gtxn[0].asset_receiver() == Addr(player_alice), 117 | Gtxn[0].asset_close_to() == Global.zero_address(), 118 | Gtxn[0].rekey_to() == Global.zero_address()) 119 | 120 | change_turn = Or(change_turn_alice_to_bob, change_turn_bob_to_alice) 121 | 122 | remove_asa_pieces = And(play_turn_type, 123 | Txn.group_index() == Int(1), 124 | Gtxn[1].type_enum() == Int(4), 125 | Gtxn[1].fee() <= tmpl_fee, 126 | Gtxn[1].xfer_asset() == Int(asa_pieces_id), 127 | Gtxn[1].asset_amount() >= Int(1), 128 | Gtxn[1].asset_amount() <= Int(asa_pieces_max_remove), 129 | Gtxn[1].asset_receiver() == Addr(asc1_sink), 130 | Gtxn[1].asset_close_to() == Global.zero_address(), 131 | Gtxn[1].rekey_to() == Global.zero_address()) 132 | 133 | play_turn = And(change_turn, remove_asa_pieces) 134 | asc1_game_table = Or(dealer, play_turn) 135 | return compileTeal(asc1_game_table, Mode.Signature) 136 | 137 | 138 | def asc1_bet_escrow_teal(algod_client, 139 | asa_pieces_total, 140 | asa_pieces_id, 141 | asa_turn_id, 142 | addr_escrow_owner, 143 | addr_adversary_player, 144 | asc1_sink, 145 | asc1_game_table, 146 | match_hours_timeout): 147 | """HELP asc1_sink_raw_teal: 148 | (AlgodClient, int, int, int, str, str, str, str, float) - Returns 149 | AlgoNim ASC1 Bet Escrow raw TEAL 150 | """ 151 | # AlgoNim Bet Escrow controls the following conditions: 152 | # 1. Opponent wins 153 | # 2. Bet escrow expires 154 | 155 | # Blockchain Parameters 156 | blockchain_params = algod_client.suggested_params() 157 | first_valid = blockchain_params.first 158 | 159 | # AlgoNim Bet Escrow expiration 160 | match_blocks_duration = int(match_hours_timeout * 3600 // 5) 161 | bet_escrow_expiry_block = first_valid + match_blocks_duration 162 | print("AlgoNim Bet Escrows Expiry block:", bet_escrow_expiry_block) 163 | 164 | # ASC1 Constants: 165 | tmpl_fee = Int(1000) 166 | 167 | # ASC1 Logic: 168 | # 1. Opponent wins 169 | change_turn = And(Global.group_size() == Int(4), 170 | Gtxn[0].type_enum() == Int(4), 171 | Gtxn[0].fee() <= tmpl_fee, 172 | Gtxn[0].xfer_asset() == Int(asa_turn_id), 173 | Gtxn[0].asset_amount() == Int(1), 174 | Gtxn[0].sender() == Addr(addr_adversary_player), 175 | Gtxn[0].asset_receiver() == Addr(addr_escrow_owner), 176 | Gtxn[0].asset_close_to() == Global.zero_address(), 177 | Gtxn[0].rekey_to() == Global.zero_address()) 178 | 179 | last_move = And(Global.group_size() == Int(4), 180 | Gtxn[1].type_enum() == Int(4), 181 | Gtxn[1].fee() <= tmpl_fee, 182 | Gtxn[1].xfer_asset() == Int(asa_pieces_id), 183 | Gtxn[1].sender() == Addr(asc1_game_table), 184 | Gtxn[1].asset_receiver() == Addr(asc1_sink), 185 | Gtxn[1].asset_close_to() == Global.zero_address(), 186 | Gtxn[1].rekey_to() == Global.zero_address()) 187 | 188 | winner_proof = And(Global.group_size() == Int(4), 189 | Gtxn[2].type_enum() == Int(4), 190 | Gtxn[2].fee() <= tmpl_fee, 191 | Gtxn[2].xfer_asset() == Int(asa_pieces_id), 192 | Gtxn[2].sender() == Addr(asc1_sink), 193 | Gtxn[2].asset_receiver() == Addr(addr_adversary_player), 194 | Gtxn[2].asset_amount() == Int(asa_pieces_total), 195 | Gtxn[2].asset_close_to() == Global.zero_address(), 196 | Gtxn[2].rekey_to() == Global.zero_address()) 197 | 198 | collect_reward = And(Global.group_size() == Int(4), 199 | Txn.group_index() == Int(3), 200 | Gtxn[3].type_enum() == Int(1), 201 | Gtxn[3].fee() <= tmpl_fee, 202 | Gtxn[3].receiver() == Addr(addr_adversary_player), 203 | Gtxn[3].amount() == Int(0), 204 | Gtxn[3].close_remainder_to() == Addr(addr_adversary_player), 205 | Gtxn[3].rekey_to() == Global.zero_address()) 206 | 207 | win = And(change_turn, last_move, winner_proof, collect_reward) 208 | 209 | # 2. Bet Escrow Timeout 210 | timeout = And(Global.group_size() == Int(1), 211 | Txn.group_index() == Int(0), 212 | Txn.type_enum() == Int(1), 213 | Txn.fee() <= tmpl_fee, 214 | Txn.receiver() == Addr(addr_escrow_owner), 215 | Txn.amount() == Int(0), 216 | Txn.close_remainder_to() == Addr(addr_escrow_owner), 217 | Txn.first_valid() > Int(bet_escrow_expiry_block), 218 | Txn.rekey_to() == Global.zero_address()) 219 | 220 | # 3. Close Bet Escrow 221 | asc1_bet_escrow = And( 222 | Cond([Global.group_size() == Int(4), win], 223 | [Global.group_size() == Int(1), timeout]), 224 | Int(1) == Int(1)) 225 | return compileTeal(asc1_bet_escrow, Mode.Signature), bet_escrow_expiry_block 226 | -------------------------------------------------------------------------------- /algonim_lib.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from algosdk import v2client, transaction 4 | 5 | 6 | def create_algod_client(print_status=False, print_version=False): 7 | """HELP create_algod_client: 8 | (bool, bool) - Returns an AlgodClient using local Node showing 9 | network status and version. $ALGORAND_DATA must be set as environment 10 | variable. 11 | """ 12 | 13 | ALGORAND_DATA = os.environ.get("ALGORAND_DATA") 14 | 15 | if not ALGORAND_DATA: 16 | raise Exception("Set the environment variable $ALGORAND_DATA from the " 17 | + "CLI entering: export ALGORAND_DATA=/path/to/data/") 18 | if not ALGORAND_DATA[-1] == "/": 19 | ALGORAND_DATA += "/" 20 | with open(ALGORAND_DATA + "algod.token", "r") as file: 21 | algod_token = file.read().splitlines() 22 | algod_token = algod_token[0] 23 | file.close() 24 | with open(ALGORAND_DATA + "algod.net", "r") as file: 25 | algod_address = file.read().splitlines() 26 | file.close() 27 | algod_address = 'http://' + algod_address[0] 28 | 29 | algod_client = v2client.algod.AlgodClient(algod_token, algod_address) 30 | 31 | try: 32 | if print_status: 33 | print("||-=-=-{ Algorand Network Status }-=-=-||") 34 | print(json.dumps(algod_client.status(), indent=4)) 35 | print("") 36 | if print_version: 37 | print("||-=-=-{ Algorand Network Version }-=-=-||") 38 | print(json.dumps(algod_client.versions(), indent=4)) 39 | print("") 40 | except Exception as e: 41 | print(e) 42 | return algod_client 43 | 44 | 45 | def wait_for_tx_confirmation(algod_client, txid): 46 | """HELP wait_for_tx_confirmation: 47 | (AlgodClient, obj) - Wait for TX confirmation and displays confirmation 48 | round. 49 | """ 50 | last_round = algod_client.status().get('last-round') 51 | while True: 52 | txinfo = algod_client.pending_transaction_info(txid) 53 | if txinfo.get('confirmed-round') and txinfo.get('confirmed-round') > 0: 54 | print("Transaction {} confirmed in round {}.".format( 55 | txid, txinfo.get('confirmed-round'))) 56 | break 57 | else: 58 | print("Waiting for confirmation...") 59 | last_round += 1 60 | algod_client.status_after_block(last_round) 61 | 62 | 63 | def unsigned_asset_send(algod_client, sender, receiver, asset_id, asa_amount, 64 | validity_range=1000): 65 | """HELP unsigned_asset_send: 66 | (AlgodClient, dict, str, int, int, int) - Returns an unsigned 67 | AssetTransferTxn. 68 | """ 69 | assert type(asa_amount) == int and validity_range <= 1000 70 | # Get network suggested params for transactions. 71 | params = algod_client.suggested_params() 72 | first_valid = params.first 73 | last_valid = first_valid + validity_range 74 | gh = params.gh 75 | min_fee = params.min_fee 76 | assert min_fee <= 1000 77 | data = { 78 | "sender": sender, 79 | "fee": min_fee, 80 | "first": first_valid, 81 | "last": last_valid, 82 | "gh": gh, 83 | "receiver": receiver, 84 | "amt": asa_amount, 85 | "index": asset_id, 86 | "flat_fee": True, 87 | } 88 | return transaction.AssetTransferTxn(**data) 89 | 90 | 91 | def unsigned_send(algod_client, addr_sender, addr_receiver, microalgo_amount, 92 | validity_range=1000): 93 | """HELP unsigned_send: 94 | (AlgodClient, str, str, int, int) - Returns an unsigned PaymentTxn. 95 | """ 96 | assert type(microalgo_amount) == int and validity_range <= 1000 97 | # Get network suggested params for transactions. 98 | params = algod_client.suggested_params() 99 | first_valid = params.first 100 | last_valid = first_valid + validity_range 101 | gh = params.gh 102 | min_fee = params.min_fee 103 | assert min_fee <= 1000 104 | 105 | data = { 106 | "sender": addr_sender, 107 | "receiver": addr_receiver, 108 | "fee": min_fee, 109 | "first": first_valid, 110 | "last": last_valid, 111 | "gh": gh, 112 | "amt": int(microalgo_amount), 113 | "flat_fee": True, 114 | } 115 | return transaction.PaymentTxn(**data) 116 | 117 | 118 | def send(algod_client, sender, addr_receiver, microalgo_amount, 119 | validity_range=1000): 120 | """HELP send: 121 | (AlgodClient, dict, str, int, int) - Executes a PaymentTxn. 122 | """ 123 | assert type(microalgo_amount) == int and validity_range <= 1000 124 | # Get network suggested params for transactions. 125 | params = algod_client.suggested_params() 126 | first_valid = params.first 127 | last_valid = first_valid + validity_range 128 | gh = params.gh 129 | min_fee = params.min_fee 130 | assert min_fee <= 1000 131 | 132 | data = { 133 | "sender": sender['pk'], 134 | "fee": min_fee, 135 | "first": first_valid, 136 | "last": last_valid, 137 | "gh": gh, 138 | "receiver": addr_receiver, 139 | "amt": int(microalgo_amount), 140 | "flat_fee": True 141 | } 142 | 143 | txn = transaction.PaymentTxn(**data) 144 | stxn = txn.sign(sender['sk']) 145 | txid = algod_client.send_transaction(stxn) 146 | print("Transaction ID:", txid) 147 | wait_for_tx_confirmation(algod_client, txid) 148 | 149 | 150 | def unsigned_closeto(algod_client, sender, closeto, validity_range=1000): 151 | """HELP unsigned_closeto: 152 | (AlgodClient, dict, str, int) - Returns an unsigned PaymentTxn with 153 | close-to account. 154 | """ 155 | # Get network suggested params for transactions. 156 | assert validity_range <= 1000 157 | params = algod_client.suggested_params() 158 | first_valid = params.first 159 | last_valid = first_valid + validity_range 160 | gh = params.gh 161 | min_fee = params.min_fee 162 | assert min_fee <= 1000 163 | 164 | data = { 165 | "sender": sender, 166 | "receiver": closeto, 167 | "fee": min_fee, 168 | "first": first_valid, 169 | "last": last_valid, 170 | "gh": gh, 171 | "amt": 0, 172 | "flat_fee": True, 173 | "close_remainder_to": closeto 174 | } 175 | return transaction.PaymentTxn(**data) 176 | -------------------------------------------------------------------------------- /algonim_moves.py: -------------------------------------------------------------------------------- 1 | import msgpack 2 | import base64 3 | from algosdk import encoding, mnemonic, transaction 4 | from algosdk.account import address_from_private_key 5 | from algonim_asa import * 6 | from algonim_asc1 import * 7 | 8 | 9 | def bet_atomic_transfer(algod_client, dealer, addr_opponent, 10 | addr_dealer_bet_escrow, addr_opponent_bet_escrow, 11 | microalgo_bet_amount): 12 | """HELP bet_atomic_transfer: 13 | (AlgodClient, dict, str, str, str, int) - Returns Bet Atomic Transfer 14 | partially signed by the Dealer, to be signed by the Opponent. 15 | """ 16 | 17 | dealer_bet_txn = unsigned_send(algod_client, dealer['pk'], 18 | addr_dealer_bet_escrow, 19 | microalgo_bet_amount) 20 | opponent_bet_txn = unsigned_send(algod_client, addr_opponent, 21 | addr_opponent_bet_escrow, 22 | microalgo_bet_amount) 23 | 24 | # Gorup Transaction 25 | gid = transaction.calculate_group_id([dealer_bet_txn, opponent_bet_txn]) 26 | dealer_bet_txn.group = gid 27 | opponent_bet_txn.group = gid 28 | 29 | dealer_bet_stxn = dealer_bet_txn.sign(dealer['sk']) 30 | return dealer_bet_stxn, opponent_bet_txn 31 | 32 | 33 | def play_last_turn(algod_client, player, addr_adversary, 34 | addr_game_table, game_table_lsig, 35 | addr_sink, sink_lsig, 36 | addr_adversary_bet_escrow, adversary_bet_escrow_lsig, 37 | asa_turn_id, asa_pieces_id, 38 | asa_pieces_amount, asa_pieces_total): 39 | """HELP play_last_turn: 40 | (AlgodClient, dict, str, str, str, str, str, str, str, int, int, int, 41 | int) - Play last turn moving ASA Pieces form the Game Table to the 42 | Sink and show the winning proof to the opponent's Bet Escrow. 43 | """ 44 | 45 | txn0 = unsigned_asset_send(algod_client, player['pk'], addr_adversary, 46 | asa_turn_id, 1) 47 | txn1 = unsigned_asset_send(algod_client, addr_game_table, addr_sink, 48 | asa_pieces_id, asa_pieces_amount) 49 | txn2 = unsigned_asset_send(algod_client, addr_sink, player['pk'], 50 | asa_pieces_id, asa_pieces_total) 51 | txn3 = unsigned_closeto(algod_client, addr_adversary_bet_escrow, 52 | player['pk']) 53 | 54 | # Gorup Transaction 55 | gid = transaction.calculate_group_id([txn0, txn1, txn2, txn3]) 56 | txn0.group = gid 57 | txn1.group = gid 58 | txn2.group = gid 59 | txn3.group = gid 60 | 61 | stxn0 = txn0.sign(player['sk']) 62 | lstxn1 = transaction.LogicSigTransaction(txn1, game_table_lsig) 63 | lstxn2 = transaction.LogicSigTransaction(txn2, sink_lsig) 64 | lstxn3 = transaction.LogicSigTransaction(txn3, adversary_bet_escrow_lsig) 65 | 66 | sgtxn = [stxn0, lstxn1, lstxn2, lstxn3] 67 | txid = algod_client.send_transactions(sgtxn) 68 | print("\nI WON !!! Arrivederci " + addr_adversary) 69 | print("Transaction ID: ", txid) 70 | wait_for_tx_confirmation(algod_client, txid) 71 | 72 | 73 | def play_turn(algod_client, player, addr_adversary, 74 | addr_game_table, game_table_lsig, 75 | addr_sink, sink_lsig, 76 | addr_adversary_bet_escrow, adversary_bet_escrow_lsig, 77 | asa_turn_id, asa_pieces_id, 78 | asa_pieces_max_remove, asa_pieces_amount, asa_pieces_total): 79 | """HELP play_turn: 80 | (AlgodClient, dict, str, str, str, str, str, str, str, int, int, int, 81 | int) - Play turn moving ASA Pieces form the Game Table to the Sink. 82 | """ 83 | 84 | game_table_info = algod_client.account_info(addr_game_table) 85 | pieces_on_table = next( 86 | (asa['amount'] for asa in game_table_info['assets'] if 87 | asa['asset-id'] == asa_pieces_id), None) 88 | 89 | if pieces_on_table > asa_pieces_max_remove: 90 | txn0 = unsigned_asset_send(algod_client, player['pk'], addr_adversary, 91 | asa_turn_id, 1) 92 | txn1 = unsigned_asset_send(algod_client, addr_game_table, addr_sink, 93 | asa_pieces_id, asa_pieces_amount) 94 | 95 | # Gorup Transaction 96 | gid = transaction.calculate_group_id([txn0, txn1]) 97 | txn0.group = gid 98 | txn1.group = gid 99 | stxn0 = txn0.sign(player['sk']) 100 | lstxn1 = transaction.LogicSigTransaction(txn1, game_table_lsig) 101 | sgtxn = [stxn0, lstxn1] 102 | txid = algod_client.send_transactions(sgtxn) 103 | print("\nRemoving", asa_pieces_amount, "pieces from the Game Table...") 104 | print("Transaction ID: ", txid) 105 | wait_for_tx_confirmation(algod_client, txid) 106 | else: 107 | play_last_turn(algod_client, player, addr_adversary, 108 | addr_game_table, game_table_lsig, 109 | addr_sink, sink_lsig, 110 | addr_adversary_bet_escrow, adversary_bet_escrow_lsig, 111 | asa_turn_id, asa_pieces_id, 112 | asa_pieces_amount, asa_pieces_total) 113 | 114 | 115 | def match_setup(algod_client, dealer_passphrase, addr_opponent, 116 | match_hours_timeout, microalgo_bet_amount, asa_pieces_total, 117 | asa_pieces_max_remove): 118 | """HELP match_setup: 119 | (AlgodClient, str, str, float, int, int, int) - Sets up a new AlgoNim 120 | match. 121 | """ 122 | 123 | # AlgoNim Players 124 | # Palyer 1 (Dealer) - Match Set Up costs about: 0.8 ALGO 125 | dealer_private_key = mnemonic.to_private_key(dealer_passphrase) 126 | dealer = {'pk': address_from_private_key(dealer_private_key), 127 | 'sk': dealer_private_key} 128 | 129 | # Player 2 (Opponent) - Must Opt-In AlgoNim ASAs to play the match 130 | opponent = {'pk': addr_opponent} 131 | 132 | print(" ") 133 | print(" _ __ ____ _____ _ ") 134 | print(" / \ [ | |_ \|_ _| (_) ") 135 | print(" / _ \ | | .--./) .--. | \ | | __ _ .--..--. ") 136 | print(" / ___ \ | | / /'`\;/ .'`\ \ | |\ \| | [ | [ `.-. .-. | ") 137 | print(" _/ / \ \_ | | \ \._//| \__. |_| |_\ |_ | | | | | | | | ") 138 | print("|____| |____|[___].',__` '.__.'|_____|\____|[___][___||__||__]") 139 | print(" ( ( __)) ") 140 | print(" by cusma") 141 | print(" ") 142 | print(" Welcome to AlgoNim, the first crypto-mini-game on Algorand! ") 143 | 144 | # Asset Create: AlgoNim ASA Pieces 145 | print("") 146 | print("Dealer creating AlgoNim ASA Piece for this match...") 147 | asa_pieces_id = asa_pieces_create(algod_client, dealer, asa_pieces_total) 148 | 149 | # Asset Create: AlgoNim ASA Turn 150 | print("") 151 | print("Dealer creating AlgoNim ASA Turn for this match...") 152 | asa_turn_id = asa_turn_create(algod_client, dealer) 153 | 154 | # TEAL: AlgoNim ASC1 Sink 155 | print("") 156 | print("Dealer writing AlgoNim ASC1 Sink TEAL for this match...") 157 | asc1_sink_source = asc1_sink_teal( 158 | asa_pieces_total, asa_pieces_id, dealer['pk'], opponent['pk']) 159 | asc1_sink_compiled = algod_client.compile(asc1_sink_source) 160 | 161 | sink_lsig = transaction.LogicSig( 162 | base64.decodebytes(asc1_sink_compiled['result'].encode())) 163 | addr_sink = asc1_sink_compiled['hash'] 164 | 165 | # Initialize AlgoNim ASC1 Sink Account with ALGO 166 | print("") 167 | print("Initializing Sink Account...") 168 | send(algod_client, dealer, addr_sink, 300000) 169 | 170 | # AlgoNim ASC1 Sink Account Opt-In 171 | print("") 172 | print("Sink Account AlgoNim ASA Piece Opt-In...") 173 | sink_opt_in_txn = unsigned_asset_send( 174 | algod_client, addr_sink, addr_sink, asa_pieces_id, 0) 175 | sink_opt_in_lstxn = transaction.LogicSigTransaction( 176 | sink_opt_in_txn, sink_lsig) 177 | txid = algod_client.send_transactions([sink_opt_in_lstxn]) 178 | print("Transaction ID:", txid) 179 | wait_for_tx_confirmation(algod_client, txid) 180 | 181 | # TEAL: AlgoNim ASC1 Game Table 182 | print("") 183 | print("Dealer writing AlgoNim ASC1 Game Table TEAL for this match...") 184 | asc1_game_table_source = asc1_game_table_teal( 185 | asa_pieces_total, asa_pieces_id, asa_pieces_max_remove, asa_turn_id, 186 | dealer['pk'], opponent['pk'], addr_sink) 187 | asc1_game_table_compiled = algod_client.compile(asc1_game_table_source) 188 | 189 | game_table_lsig = transaction.LogicSig( 190 | base64.decodebytes(asc1_game_table_compiled['result'].encode())) 191 | addr_game_table = asc1_game_table_compiled['hash'] 192 | 193 | # Initialize AlgoNim ASC1 Game Table Account with ALGO 194 | print("") 195 | print("Initializing Game Table Account...") 196 | send(algod_client, dealer, addr_game_table, 300000) 197 | 198 | # Dealer Sets Up the Game Table with AlgoNim ASA Pieces 199 | print("") 200 | print("Dealer distributing ASA Pieces on the Game Table...") 201 | gt_opt_in_txn = unsigned_asset_send( 202 | algod_client, addr_game_table, addr_game_table, asa_pieces_id, 0) 203 | deal_pieces_txn = unsigned_asset_send( 204 | algod_client, dealer['pk'], addr_game_table, asa_pieces_id, 205 | asa_pieces_total) 206 | 207 | # Dealer Gorup Transaction 208 | dealer_gid = transaction.calculate_group_id([gt_opt_in_txn, 209 | deal_pieces_txn]) 210 | gt_opt_in_txn.group = dealer_gid 211 | deal_pieces_txn.group = dealer_gid 212 | gt_opt_in_lstxn = transaction.LogicSigTransaction(gt_opt_in_txn, 213 | game_table_lsig) 214 | deal_pieces_stxn = deal_pieces_txn.sign(dealer['sk']) 215 | dealer_sgtxn = [gt_opt_in_lstxn, deal_pieces_stxn] 216 | txid = algod_client.send_transactions(dealer_sgtxn) 217 | print("Transaction ID: ", txid) 218 | wait_for_tx_confirmation(algod_client, txid) 219 | 220 | # TEAL: AlgoNim ASC1 Bet Escrow 221 | print("") 222 | print("Dealer writing AlgoNim ASC1 Bet Escrow TEAL for Palyer 1...") 223 | asc1_dealer_bet_escrow_source, dealer_bet_escrow_expiry_block = asc1_bet_escrow_teal( 224 | algod_client, asa_pieces_total, asa_pieces_id, asa_turn_id, 225 | dealer['pk'], opponent['pk'], addr_sink, addr_game_table, 226 | match_hours_timeout) 227 | asc1_dealer_bet_escrow_compiled = algod_client.compile( 228 | asc1_dealer_bet_escrow_source) 229 | 230 | dealer_bet_escrow_lsig = transaction.LogicSig( 231 | base64.decodebytes(asc1_dealer_bet_escrow_compiled['result'].encode())) 232 | addr_dealer_bet_escrow = asc1_dealer_bet_escrow_compiled['hash'] 233 | 234 | print("") 235 | print("Dealer writing AlgoNim ASC1 Bet Escrow TEAL for Palyer 2...") 236 | asc1_opponent_bet_escrow_source, opponent_bet_escrow_expiry_block = asc1_bet_escrow_teal( 237 | algod_client, asa_pieces_total, asa_pieces_id, asa_turn_id, 238 | opponent['pk'], dealer['pk'], addr_sink, addr_game_table, 239 | match_hours_timeout) 240 | asc1_opponent_bet_escrow_compiled = algod_client.compile( 241 | asc1_opponent_bet_escrow_source) 242 | 243 | opponent_bet_escrow_lsig = transaction.LogicSig( 244 | base64.decodebytes(asc1_opponent_bet_escrow_compiled['result'].encode())) 245 | addr_opponent_bet_escrow = asc1_opponent_bet_escrow_compiled['hash'] 246 | 247 | # Initialize AlgoNim ASC1 Escrows with 0.1 ALGO 248 | print("") 249 | print("Initializing Bet Escrow Accounts...") 250 | send(algod_client, dealer, addr_dealer_bet_escrow, 100000) 251 | print("") 252 | send(algod_client, dealer, addr_opponent_bet_escrow, 100000) 253 | 254 | # Creating Bet Atomic Transfer to be signed by the Opponent 255 | dealer_bet_stxn, opponent_bet_txn = bet_atomic_transfer( 256 | algod_client, dealer, opponent['pk'], addr_dealer_bet_escrow, 257 | addr_opponent_bet_escrow, microalgo_bet_amount) 258 | 259 | match_data = {} 260 | match_data['dealer'] = dealer['pk'] 261 | match_data['opponent'] = opponent['pk'] 262 | match_data['asa_pieces_id'] = asa_pieces_id 263 | match_data['asa_pieces_max_remove'] = asa_pieces_max_remove 264 | match_data['asa_turn_id'] = asa_turn_id 265 | match_data['sink'] = addr_sink 266 | match_data['sink_lsig'] = encoding.msgpack_encode(sink_lsig) 267 | match_data['game_table'] = addr_game_table 268 | match_data['game_table_lsig'] = encoding.msgpack_encode(game_table_lsig) 269 | match_data['dealer_bet_escrow'] = addr_dealer_bet_escrow 270 | match_data['dealer_bet_escrow_lsig'] = encoding.msgpack_encode( 271 | dealer_bet_escrow_lsig) 272 | match_data['opponent_bet_escrow'] = addr_opponent_bet_escrow 273 | match_data['opponent_bet_escrow_lsig'] = encoding.msgpack_encode( 274 | opponent_bet_escrow_lsig) 275 | match_data['dealer_bet_stxn'] = encoding.msgpack_encode(dealer_bet_stxn) 276 | match_data['opponent_bet_txn'] = encoding.msgpack_encode(opponent_bet_txn) 277 | match_data['microalgo_bet_amount'] = microalgo_bet_amount 278 | match_data['match_hours_timeout'] = match_hours_timeout 279 | match_data['dealer_bet_escrow_expiry'] = dealer_bet_escrow_expiry_block 280 | match_data['opponent_bet_escrow_expiry'] = opponent_bet_escrow_expiry_block 281 | match_data_bytes = msgpack.packb(match_data) 282 | match_data_fname = 'algonim.match' 283 | 284 | with open(match_data_fname, "wb") as f: 285 | f.write(match_data_bytes) 286 | f.close() 287 | 288 | print(" ") 289 | print(" _ _ ") 290 | print(" /\/\ __ _| |_ ___| |__ _ ") 291 | print(" / \ / _` | __/ __| '_ \ (_)") 292 | print("/ /\/\ \ (_| | || (__| | | | _ ") 293 | print("\/ \/\__,_|\__\___|_| |_| (_)") 294 | print(" ") 295 | print("MATCH DURATION:\t\t", match_data['match_hours_timeout'] * 60, "min") 296 | print("PIECES ON GAME TABLE:\t", asa_pieces_total, "\n") 297 | print("RULES:") 298 | print("1. Players on each turn must remove at least 1 ASA Piece") 299 | print("2. Players on each turn must remove at most", 300 | match_data['asa_pieces_max_remove'], "ASA Piece") 301 | print("3. Who removes the last ASA Piece form the Game Table wins the " 302 | + "match!\n") 303 | print("Player 1 - Dealer:\t" + match_data['dealer']) 304 | print("Player 2 - Opponent:\t" + match_data['opponent'], "\n") 305 | print("AlgoNim ASA Pieces ID:\t", match_data['asa_pieces_id']) 306 | print("AlgoNim ASA Turn ID:\t", match_data['asa_turn_id'], "\n") 307 | print("AlgoNim Sink Account:\t\t\t" + match_data['sink']) 308 | print("AlgoNim Game Table Account:\t\t" + match_data['game_table']) 309 | print("AlgoNim Bet Escrow Player 1 Account:\t" 310 | + match_data['dealer_bet_escrow']) 311 | print("AlgoNim Bet Escrow Player 2 Account:\t" 312 | + match_data['opponent_bet_escrow']) 313 | print("\nSend 'algonim.match' file to your opponent join the match!\n") 314 | print("May the best win!\n") 315 | 316 | 317 | def close_bet_escrow(algod_client, addr_bet_escrow, addr_owner, 318 | bet_escrow_lsig): 319 | """HELP close_escrow: 320 | (AlgodClient, str, str, LogicSig) - Closes the expired Bet Escrow. 321 | """ 322 | 323 | txn0 = unsigned_closeto(algod_client, addr_bet_escrow, addr_owner) 324 | lstxn0 = transaction.LogicSigTransaction(txn0, bet_escrow_lsig) 325 | txid = algod_client.send_transactions([lstxn0]) 326 | print("\nClosing expired Bet Escrow: " + addr_bet_escrow) 327 | print("Transaction ID: ", txid) 328 | wait_for_tx_confirmation(algod_client, txid) 329 | --------------------------------------------------------------------------------