Skip to content

Instantly share code, notes, and snippets.

@tacyarg
Last active January 15, 2019 15:08
Show Gist options
  • Save tacyarg/1bcfa7268f00e3d78044f977d8d58e00 to your computer and use it in GitHub Desktop.
Save tacyarg/1bcfa7268f00e3d78044f977d8d58e00 to your computer and use it in GitHub Desktop.
jackpot game methods, state-machine and ticket generation.
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)
}
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)
}
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