├── sleth ├── __init__.py ├── constants.py ├── reels.py └── cli.py ├── frontend ├── .gitignore ├── sounds │ ├── win.wav │ └── reel_stop.wav ├── images │ ├── background.png │ ├── five_lines.png │ ├── one_line.png │ ├── reels_bg.png │ ├── three_lines.png │ └── reddit_icons_small.png ├── .jshintrc ├── templates │ ├── reveal-value.html │ ├── progress-bar.html │ ├── stats-panel.html │ ├── prev-rounds-panel.html │ ├── player-bar.html │ ├── home.html │ ├── round-panel.html │ └── sleth.html ├── README.md ├── js │ ├── app.js │ ├── home.js │ ├── config.js │ ├── sounds.js │ ├── directives.js │ ├── slots.js │ ├── reels.js │ ├── game.js │ ├── sleth.js │ └── lib │ │ └── web3.min.js ├── bower.json ├── sleth.abi.json ├── css │ └── slots.css ├── index.html ├── ng-blockwatch.html ├── slots.html └── paytable.html ├── dev_requirements.txt ├── contracts ├── simple.yaml ├── sleth.yaml ├── simple.se ├── sleth.se └── commit_reveal_entropy.se ├── cli.py ├── requirements.txt ├── tox.ini ├── setup.py ├── .travis.yml ├── gulpfile.js ├── test ├── conftest.py ├── eth_tools.py ├── test_simple.py ├── test_stops.py ├── test_ecrecover.py ├── test_reels.py ├── test_log.py ├── test_commit_reveal_entropy_contract.py └── test_sleth_contract.py ├── .gitignore └── README.md /sleth/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | pytest 3 | pytest-xdist 4 | -------------------------------------------------------------------------------- /frontend/sounds/win.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/sounds/win.wav -------------------------------------------------------------------------------- /contracts/simple.yaml: -------------------------------------------------------------------------------- 1 | - 2 | deploy: 3 | simple: 4 | contract: simple.se 5 | gas: 100000 6 | -------------------------------------------------------------------------------- /frontend/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/background.png -------------------------------------------------------------------------------- /frontend/images/five_lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/five_lines.png -------------------------------------------------------------------------------- /frontend/images/one_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/one_line.png -------------------------------------------------------------------------------- /frontend/images/reels_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/reels_bg.png -------------------------------------------------------------------------------- /frontend/sounds/reel_stop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/sounds/reel_stop.wav -------------------------------------------------------------------------------- /frontend/images/three_lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/three_lines.png -------------------------------------------------------------------------------- /cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sleth.cli 4 | 5 | if __name__ == '__main__': 6 | sleth.cli.main() 7 | -------------------------------------------------------------------------------- /frontend/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "browserify": true, 4 | "devel": true, 5 | "predef": ["angular"] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/images/reddit_icons_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jorisbontje/sleth/HEAD/frontend/images/reddit_icons_small.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | https://github.com/ethereum/serpent/tarball/develop 2 | https://github.com/ethereum/pyethereum/tarball/develop 3 | pyepm 4 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E302 3 | max-line-length = 160 4 | exclude = frontend/* 5 | 6 | [pytest] 7 | norecursedirs = .git frontend 8 | -------------------------------------------------------------------------------- /sleth/constants.py: -------------------------------------------------------------------------------- 1 | CONTRACT_FILE = 'contracts/sleth.se' 2 | 3 | CONTRACT_GAS = 1028711 4 | SPIN_GAS = 163000 5 | CLAIM_GAS = 120000 6 | 7 | ETHER = 10 ** 18 8 | -------------------------------------------------------------------------------- /frontend/templates/reveal-value.html: -------------------------------------------------------------------------------- 1 | 2 | {{value}} 3 | ? 4 | 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup(name='sleth', 6 | version='1.0', 7 | description='Ethereum Slot Machine', 8 | packages=['sleth']) 9 | -------------------------------------------------------------------------------- /contracts/sleth.yaml: -------------------------------------------------------------------------------- 1 | - 2 | deploy: 3 | sleth: 4 | contract: sleth.se 5 | gas: 60000 6 | # 7 | #- 8 | # deploy: 9 | # commit_reveal_entropy: 10 | # contract: commit_reveal_entropy.se 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | install: 5 | - pip install -r requirements.txt 6 | - pip install -r dev_requirements.txt 7 | - pip install -e . 8 | script: 9 | - py.test -vvrs 10 | - flake8 11 | -------------------------------------------------------------------------------- /contracts/simple.se: -------------------------------------------------------------------------------- 1 | data owner 2 | data counter 3 | 4 | def init(): 5 | self.owner = msg.sender 6 | self.counter = 1 7 | 8 | def incr(): 9 | self.counter += 1 10 | 11 | def get_counter(): 12 | return(self.counter) 13 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | License Info 2 | 3 | The original Karma-Slots code is (C) 2012 Clint Bellanger. Released under the MIT license. 4 | The original Karma-Slots art is (C) 2012 Clint Bellanger. Released under CC-BY 3.0. 5 | The original Karma-Slots sounds are (C) 2012 Brandon Morris. Released under CC-BY 3.0 6 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var deploy = require('gulp-gh-pages'); 3 | 4 | /** 5 | * Push build to gh-pages 6 | */ 7 | gulp.task('deploy', function () { 8 | return gulp.src(['./frontend/**/*', '!./frontend/node_modules/**']) 9 | .pipe(deploy({cacheDir: '../sleth-gh-pages/'})) 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/templates/progress-bar.html: -------------------------------------------------------------------------------- 1 |
| Contract | {{contract | limitTo : 10}} |
|---|---|
| Contract Balance | {{stats.slethBalance | number : 4}} ETH |
| Total Spins | {{stats.total_spins}} |
| Total Coins Bet | {{stats.total_coins_bet}} |
| Total Coins Won | {{stats.total_coins_won}} |
| Payout Percentage | {{stats.total_coins_won / stats.total_coins_bet * 100| number :2}} % |
| Round | 8 |Player | 9 |Block | 10 |Bet | 11 |Prize | 12 |
|---|---|---|---|---|
| {{round.number}} | 15 |{{round.player | limitTo : 10}} | 16 |{{round.block}} | 17 |{{round.bet}} | 18 |{{round.result}} | 19 |
Are you ready to play some slots? Please enter the Sleth contract address below so we can connect you to your slot machine!
5 | 6 | 15 | 16 || Block | {{round.block}} | 8 |Status | 9 |10 | NEW 11 | SPINNING 12 | DONE 13 | EXPIRED 14 | | 15 |
|---|---|---|---|
| Bet | {{round.bet}} | 18 |Result | 19 ||
| Entropy |
23 | |
25 | RND |
22 |
23 | You won {{reward.payout}}!
24 |
25 |
26 | Line {{line}} pays {{partial_payout}}
27 |
28 |
by Clint Bellanger and Brandon Morris
45 |
5 |
6 | ## First game of sleth
7 |
8 | Lets create the `sleth` Ethereum Slot contract:
9 | ```
10 | $ ./cli.py create
11 | Contract is available at 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
12 | Waiting for next block to be mined..
13 | INFO:pyepm.api Ready! Mining took 10s
14 | ```
15 |
16 | The contract is created under the account `0xc6c97de34c2b52b929baa21e662196b9e9e03fe0`. Now lets deposit `1000` Ether into the slot machine:
17 | ```
18 | $ ./cli.py deposit 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 1000
19 | ```
20 |
21 | Getting information about the current player:
22 | ```
23 | $ ./cli.py get_current_player 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
24 | Current round: 0
25 | Balance: 1000 ether
26 | ```
27 |
28 | Now that we have some coins to play with, lets give it a spin; betting `5` (letting us play on 5 lines):
29 | ```
30 | $ ./cli.py spin 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 5
31 | ```
32 |
33 | Our bet is deducted from the balance, and we are playing in game round `1`:
34 | ```
35 | $ ./cli.py get_current_player 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
36 | Current round: 1
37 | Balance: 995 ether
38 | ```
39 |
40 | Knowing that we are in round `1`, we can check out its status:
41 | ```
42 | $ ./cli.py get_round 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 1
43 | Player: 0x9ecb3cc2e0d7eef7485a5234767b313c2f24b582L
44 | Block: 4823
45 | Timestamp: 1421155818
46 | Bet: 5
47 | Result: 0
48 | Entropy: 0
49 | Status: 1
50 | ```
51 |
52 | The round `1` is still pending (status=1), lets claim it! (for testing purposes we can provide the random seed `12345`):
53 | ```
54 | $ ./cli.py claim 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 1 12345
55 | ```
56 |
57 | Now we'll inspect the results of round `1` again:
58 | ```
59 | $ ./cli.py get_round 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 1
60 | Player: 0x9ecb3cc2e0d7eef7485a5234767b313c2f24b582L
61 | Block: 4823
62 | Timestamp: 1421155818
63 | Bet: 5
64 | Result: 0
65 | Entropy: 12345
66 | Status: 2
67 | ```
68 |
69 | Too bad, we didn't win anything...
70 |
71 | Lets play again!
72 | ```
73 | $ ./cli.py spin 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 5
74 |
75 | $ ./cli.py get_current_player 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
76 | Current round: 2
77 | Balance: 990 ether
78 |
79 | $ ./cli.py claim 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 2 23888
80 |
81 | $ ./cli.py get_round 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 2
82 | Player: 0x9ecb3cc2e0d7eef7485a5234767b313c2f24b582L
83 | Block: 4826
84 | Timestamp: 1421155937
85 | Bet: 5
86 | Result: 32
87 | Entropy: 23888
88 | Status: 2
89 |
90 | $ ./cli.py get_current_player 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
91 | Current round: 2
92 | Balance: 1022 ether
93 | ```
94 |
95 | Yeehaa! We won 32 ether :) Time to withdraw:
96 |
97 | ```
98 | $ ./cli.py withdraw 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0 1022
99 |
100 | $ ./cli.py get_current_player 0xc6c97de34c2b52b929baa21e662196b9e9e03fe0
101 | Current round: 2
102 | Balance: 0 ether
103 | ```
104 |
--------------------------------------------------------------------------------
/frontend/js/slots.js:
--------------------------------------------------------------------------------
1 | /**
2 |
3 | Copyright (c) 2015 Joris Bontje
4 | Copyright (c) 2012 Clint Bellanger
5 |
6 | MIT License:
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13 |
14 |
15 | Sounds by Brandon Morris (CC-BY 3.0)
16 | Art by Clint Bellanger (CC-BY 3.0)
17 |
18 | */
19 |
20 | "use strict";
21 |
22 | var app = angular.module('slots', ['slots.config', 'slots.game', 'slots.reels']);
23 |
24 | app.controller("SlotsController", ['$scope', '$interval', 'config', 'game', function($scope, $interval, config, game) {
25 |
26 | $scope.canvasSize = 160 * config.reel_scale;
27 | $scope.infoTop = 16 * config.reel_scale;
28 |
29 | // sync with blockchain
30 | $scope.credits = config.starting_credits;
31 |
32 | $interval(function() {
33 | game.logic();
34 | }, 1000 / config.FPS);
35 |
36 | $scope.generateEntropy = function() {
37 | $scope.entropy = Math.floor(Math.random() * Math.pow(32, 3));
38 | };
39 |
40 | //---- Input Functions ---------------------------------------------
41 |
42 | $scope.handleKey = function(evt) {
43 | if (evt.keyCode === 32) { // spacebar
44 | if (game.state === game.STATE_SPINMAX) {
45 | $scope.stop();
46 | return;
47 | }
48 | if (game.state !== game.STATE_NEW && game.state !== game.STATE_REST) return;
49 |
50 | if ($scope.credits >= 5) $scope.spin(5);
51 | else if ($scope.credits >= 3) $scope.spin(3);
52 | else if ($scope.credits >= 1) $scope.spin(1);
53 |
54 | }
55 | };
56 |
57 | $scope.spin = function(line_choice) {
58 | if (game.state !== game.STATE_NEW && game.state !== game.STATE_REST) return;
59 | if ($scope.credits < line_choice) return;
60 |
61 | $scope.credits -= line_choice;
62 | game.spin(line_choice);
63 | $scope.spinning = true;
64 | $scope.generateEntropy();
65 | };
66 |
67 | $scope.stop = function() {
68 | if (game.state !== game.STATE_SPINMAX) return;
69 |
70 | // calculate the final results now, so that spindown is ready
71 | game.set_stops($scope.entropy);
72 | };
73 |
74 | $scope.calc_payout_percentage = function() {
75 | var totalPayout = 0;
76 | var bet = 5;
77 | var total_positions = 32768;
78 | for (var entropy = 0; entropy < total_positions; entropy++) {
79 | game.set_stops(entropy);
80 | var result = game.get_result();
81 | var reward = game.calc_reward(bet, result);
82 | totalPayout += reward.payout;
83 | }
84 | console.log("payout percentage = ", totalPayout / (bet * total_positions) * 100);
85 | };
86 |
87 | $scope.$on('slots:reward', function(evt, reward) {
88 | /*if (reward.payout > 0) {
89 | sounds.playWin();
90 | }*/
91 | $scope.credits += reward.payout;
92 | $scope.reward = reward;
93 | $scope.spinning = false;
94 | });
95 |
96 | }]);
97 |
--------------------------------------------------------------------------------
/sleth/reels.py:
--------------------------------------------------------------------------------
1 | from collections import Counter
2 | from functools import reduce
3 | import operator
4 | import random
5 |
6 | # 'g' won
7 | # 'y' yen
8 | # 'v' dollar
9 | # 'p' euro
10 | # 'l' pound
11 | # 'H' miner
12 | # 'L' asic
13 | # 'S' ghost
14 | # 'J' liberty
15 | # '*' wild
16 | # '!' bitcoin
17 |
18 | REELS = [
19 | 'lSgypJLyg!LvHly*vpHyvgplJg',
20 | 'vplygvSg!vy*pgHlpHgLylLSyJ',
21 | 'vgySLHpJ*SJgHLpylgv!pHvygl',
22 | 'vplyvLygHlpyJvSglHgp!Sv*JL',
23 | 'L!yvLlpgvpHSg*vlgyHJLSglyp'
24 | ]
25 |
26 | MAX_RANDOM = reduce(operator.mul, (len(reel) for reel in REELS), 1)
27 |
28 | PAYOUT = {
29 | 'g': [0.12, 0.4, 2], # won
30 | 'y': [0.16, 0.6, 2.4], # yen
31 | 'v': [0.2, 0.8, 3], # dollar
32 | 'p': [0.2, 1, 3.2], # euro
33 | 'l': [0.2, 1.2, 4], # pound
34 | 'H': [0.4, 3, 16], # miner
35 | 'L': [1, 4, 18], # asic
36 | 'S': [1.2, 8, 26], # ghost
37 | 'J': [2, 10, 40] # liberty
38 | }
39 |
40 | PAYLINES = [
41 | [1, 1, 1, 1, 1],
42 | [0, 0, 0, 0, 0],
43 | [2, 2, 2, 2, 2],
44 | [0, 1, 2, 1, 0],
45 | [2, 1, 0, 1, 2],
46 |
47 | [0, 0, 1, 0, 0],
48 | [2, 2, 1, 2, 2],
49 | [1, 2, 2, 2, 1],
50 | [1, 0, 0, 0, 1],
51 | [1, 0, 1, 0, 1],
52 |
53 | [1, 2, 1, 2, 1],
54 | [0, 1, 0, 1, 0],
55 | [2, 1, 2, 1, 2],
56 | [1, 1, 0, 1, 1],
57 | [1, 1, 2, 1, 1],
58 |
59 | [0, 1, 1, 1, 0],
60 | [2, 1, 1, 1, 2],
61 | [2, 2, 1, 0, 0],
62 | [0, 0, 1, 2, 2],
63 | [0, 2, 0, 2, 0],
64 |
65 | [2, 0, 2, 0, 2],
66 | [0, 2, 2, 2, 0],
67 | [2, 0, 0, 0, 2],
68 | [0, 0, 2, 0, 0],
69 | [2, 2, 0, 2, 2]
70 | ]
71 |
72 | def wheel_frequency(wheel):
73 | freq = Counter()
74 | for char in wheel:
75 | freq[char] += 1
76 | return freq
77 |
78 | def rnd_positions(reels, rnd):
79 | reel1_div = len(reels[1]) * len(reels[2]) * len(reels[3]) * len(reels[4])
80 | reel2_div = len(reels[2]) * len(reels[3]) * len(reels[4])
81 | reel3_div = len(reels[3]) * len(reels[4])
82 | reel4_div = len(reels[4])
83 |
84 | reel1_pos = rnd / reel1_div
85 | reel1_rem = rnd % reel1_div
86 | reel2_pos = reel1_rem / reel2_div
87 | reel2_rem = reel1_rem % reel2_div
88 | reel3_pos = reel2_rem / reel3_div
89 | reel3_rem = reel2_rem % reel3_div
90 | reel4_pos = reel3_rem / reel4_div
91 | reel4_rem = reel3_rem % reel4_div
92 | reel5_pos = reel4_rem
93 |
94 | return reel1_pos, reel2_pos, reel3_pos, reel4_pos, reel5_pos
95 |
96 | def position_symbols(reels, pos):
97 | result = []
98 | for i in range(0, len(reels)):
99 | double_reel = reels[i] * 2
100 | symbols = double_reel[pos[i]:pos[i] + 3]
101 | result.append(symbols)
102 | return tuple(result)
103 |
104 |
105 | def find_paylines(symbols):
106 | result = []
107 | for payline_nr, payline in enumerate(PAYLINES):
108 | # print "PAYLINE", payline_nr, payline
109 |
110 | line = ''
111 | for pos, offset in enumerate(payline):
112 | line += symbols[pos][offset]
113 |
114 | symbol = line[0]
115 | cnt = 0
116 | if line.startswith(symbol * 5):
117 | cnt = 5
118 | elif line.startswith(symbol * 4):
119 | cnt = 4
120 | elif line.startswith(symbol * 3):
121 | cnt = 3
122 |
123 | if cnt:
124 | if symbol in PAYOUT:
125 | payout = PAYOUT[symbol][cnt - 3]
126 | result.append((payline_nr + 1, symbol, cnt, payout))
127 | else:
128 | print "Symbol", symbol, "not found in paytable"
129 | return result
130 |
131 | def sum_payline(paylines):
132 | return sum(payout for line, symbol, cnt, payout in paylines)
133 |
134 | def outcome(rnd):
135 | return sum_payline(find_paylines(position_symbols(REELS, rnd_positions(REELS, rnd))))
136 |
137 | def main():
138 | total = 0
139 | for i in range(0, 10000):
140 | rnd = random.SystemRandom().randint(0, MAX_RANDOM)
141 | out = outcome(rnd)
142 | print i, rnd, out
143 | total += out
144 |
145 | print "TOTAL", total
146 |
147 | if __name__ == '__main__':
148 | main()
149 |
--------------------------------------------------------------------------------
/frontend/templates/sleth.html:
--------------------------------------------------------------------------------
1 | Spinning...
51 |Spinning...
58 |Claiming results...
65 |85 | 86 | Pay Table 87 | 88 |
89 |
51 |
57 |
63 |