Last active
January 15, 2019 15:08
-
-
Save tacyarg/1bcfa7268f00e3d78044f977d8d58e00 to your computer and use it in GitHub Desktop.
jackpot game methods, state-machine and ticket generation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const generateRandomColor = function() { | |
var letters = '0123456789ABCDEF' | |
var color = '#' | |
for (var i = 0; i < 6; i++) { | |
color += letters[Math.floor(Math.random() * 16)] | |
} | |
return color | |
} | |
function canJoin(gameid, bet) { | |
const game = get(gameid) | |
assert(lodash.isObject(bet), 'Invalid bet object!') | |
assert(bet.value > 0, 'Invalid bet value!') | |
assert(bet.type == 'items', 'This game only accepts bets containing items.') | |
// check for valid game state | |
assert( | |
game.state == 'open' || game.state == 'running', | |
'The game has ended.' | |
) | |
// check against the game config | |
assert( | |
game.config.betItemLimit >= lodash.size(bet.items), | |
`You cannot bet more than ${game.config.itemLimit} in this game!` | |
) | |
assert( | |
game.config.betValueMax >= bet.value, | |
`You cannot bet more than ${game.config.betValueMax} in this game!` | |
) | |
assert( | |
game.config.betValueMin <= bet.value, | |
`You cannot bet less than ${game.config.betValueMin} in this game!` | |
) | |
if (game.bets[bet.userid]) { | |
const userBets = lodash.groupBy(game.bets, 'userid') | |
const totalBet = lodash.sumBy(userBets, 'value') | |
assert( | |
game.config.betValueMax >= totalBet, | |
`You cannot bet more than ${game.config.betValueMax} in this game!` | |
) | |
} | |
const betids = lodash.map(bet.items, 'id') | |
const duplicate = lodash.find(game.items, i => betids.includes(i.id)) | |
assert(!duplicate, 'One or more of your items are duplicates!') | |
return game | |
} | |
function join(gameid, bet) { | |
canJoin(gameid, bet) | |
let game = get(gameid) | |
bet.joined = Date.now() | |
bet.color = colors[game.bets.length] || generateRandomColor() | |
game.bets.push(bet) | |
bet.items.forEach(i => game.items.push(i)) | |
game.value += bet.value | |
//TODO: adjust timeleft for "kickback" effect | |
return set(game) | |
} | |
// NOTE: outcome should be drawn in the service layer | |
function draw(gameid, provable) { | |
assert(lodash.isObject(provable), 'Invalid outcome') | |
assert(provable.outcome, 'provable outcome is missing!') | |
let game = get(gameid) | |
assert(!game.winner, 'game already has a set winner!') | |
// get winning percentage to be between 0 -> 1 | |
// take result times the total game value | |
// fetch winner | |
// NOTE: ticketrange and game value should match | |
const tickets = utils.generateTickets(game.bets) | |
// winning "percentage". We must display this value to user and on verification. | |
game.provable = provable | |
game.provable.winningTicket = provable.outcome * game.value | |
// go through each game entry and find the winner | |
const winningBet = lodash.find(tickets.entries, function(entry) { | |
return ( | |
entry.start <= game.provable.winningTicket && | |
entry.end > game.provable.winningTicket | |
) | |
}) | |
game.winningBet = winningBet | |
game.winner = winningBet.userid | |
// set state to rake, then rake to payout | |
game = Stateful(game).setState('rake') | |
return set(game) | |
} | |
function rake(gameid) { | |
let game = get(gameid) | |
// winner's items. | |
let skipList = lodash.map(game.winningBet.items, 'id') | |
// build array of the items we need to payout. | |
const items = lodash.filter(game.items, item => { | |
return !lodash.includes(skipList, item.id) | |
}) | |
// traverse game items | |
// napsack items closest to the rake value | |
// mark them as rake | |
game = Stateful(game).setState('payout') | |
return set(game) | |
} | |
function payout(gameid) { | |
let game = get(gameid) | |
assert(game.state === 'payout', 'Invalid game state') | |
assert(game.winner, 'no winner is defined for this game') | |
assert(game.bets.length > 1, 'cannot payout a game with only one player') | |
// create payout | |
// set state | |
// skip rake items and winner's items. | |
let skipList = lodash.map(game.rake, 'id') | |
// build array of the items we need to payout. | |
const items = lodash.filter(game.items, item => { | |
return !lodash.includes(skipList, item.id) | |
}) | |
// add the bet to the payouts array. | |
game.payouts.push({ | |
...game.winningBet, | |
items, | |
}) | |
game = Stateful(game).setState('ended') | |
return set(game) | |
} | |
function refund(gameid) { | |
let game = get(gameid) | |
// create array of bets refunded | |
game.bets.forEach(i => game.payouts.push(i)) | |
// set state | |
game = Stateful(game).setState('refund') | |
return set(game) | |
} | |
function cancel(gameid, reason) { | |
let game = get(gameid) | |
assert(game.state == 'open', 'The game has ended.') | |
assert(game.state == 'running', 'The game has ended.') | |
game.cancelReason = reason || 'Admin cancelled game' | |
game = Stateful(game).setState('cancel') | |
return set(game) | |
} | |
function setTimeleft(gameid, timeleft) { | |
assert(timeleft, 'must define timeleft, how many secconds remaining') | |
let game = get(gameid) | |
if(timeleft < 0) timeleft = 0 | |
game.timeleft = timeleft | |
return set(game) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const FSM = require('fsm') | |
module.exports = ({ cache, provable }) => { | |
const handlers = { | |
open: game => { | |
console.log('tick open') | |
if (game.bets.length < 2) return | |
return cache.setState.emit(game.id, 'running') | |
}, | |
running: async game => { | |
console.log("tick running") | |
const [lastState] = game.history | |
const endTime = game.config.duration + lastState.updated | |
const timeleft = parseInt(endTime - Date.now()) | |
console.log(timeleft / 1000) | |
if (endTime > Date.now()) { | |
return cache.setTimeleft.emit(game.id, timeleft) | |
} | |
return cache.setState.emit(game.id, 'draw') | |
}, | |
draw: async game => { | |
const outcome = await provable.real() | |
const newState = await cache.draw(game.id, outcome) | |
console.log(newState.id, newState.provable) | |
return cache.setState.emit(game.id, 'payout') | |
}, | |
payout: async game => { | |
const result = await cache.payout(game.id) | |
// TODO: emit payout/escrow release | |
return result | |
}, | |
ended: async game => { | |
await cache.update(game.id, {done: true}) | |
return cache.remove.emit(game.id) | |
}, | |
catch(err) { | |
console.log('jackpot fsm error', err) | |
}, | |
} | |
return FSM(handlers) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const lodash = require('lodash') | |
exports.generateTickets = function(bets) { | |
var range = 0 | |
// sort bets by time joined | |
const sortedBets = lodash.sortBy(bets, bet => bet.joined) | |
// create all ticket entries | |
const entries = sortedBets.reduce((memo, bet) => { | |
// place entry | |
memo.push({ | |
userid: bet.userid, | |
betid: bet.id, | |
start: range, // ticket range start | |
end: range + bet.value, // ticket range end | |
}) | |
// set new range end | |
range += bet.value | |
return memo | |
}, []) | |
return { range, entries } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment