diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bde787..db32eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ A preview of major changes can be found in the Wiki ([Latest Changes](https://gi - New set of larger Compact buttons - Ability to configure: "Announcement Volume", "Auto Busting" and "Auto Leg Finish" from frontend - Holding score buttons to score same dart three times +- Support `Max Rounds` to play for `x01` - Lots of new Badges #### Changed diff --git a/routes/index.js b/routes/index.js index 6c7f674..21dd2fe 100644 --- a/routes/index.js +++ b/routes/index.js @@ -31,7 +31,8 @@ router.get('/', function (req, res, next) { outshots: outshots.data, lives: [{ id: 1, name: 1 }, { id: 3, name: 3 }, { id: 5, name: 5 }, { id: 7, name: 7 }, { id: 10, name: 10 }], points_to_win: [{ id: 1, name: 1 }, { id: 2, name: 2 }, { id: 3, name: 3 }, { id: 4, name: 4 }, { id: 5, name: 5 }], - max_rounds: [{ id: -1, name: 'Unlimited' }, { id: 3, name: 3 }, { id: 5, name: 5 }, { id: 7, name: 7 }, { id: 10, name: 10 }, { id: 15, name: 15 }], + max_rounds_170: [{ id: -1, name: '-' }, { id: 3, name: 3 }, { id: 5, name: 5 }, { id: 7, name: 7 }, { id: 10, name: 10 }, { id: 15, name: 15 }], + max_rounds_x01: [{ id: -1, name: '-' }, { id: 10, name: 10 }, { id: 12, name: 12 }, { id: 16, name: 16 }, { id: 20, name: 20 }, { id: 30, name: 30 }], venues: venues.data, stakes: oweTypes.data, offices: offices.data, diff --git a/routes/legs.js b/routes/legs.js index 668bc83..b369327 100644 --- a/routes/legs.js +++ b/routes/legs.js @@ -173,6 +173,60 @@ router.put('/:id/order', function (req, res, next) { }); }); +/** Method to finish a leg */ +router.put('/:id/finish', function (req, res, next) { + let legId = req.params.id; + axios.put(`${req.app.locals.kcapp.api}/leg/${legId}/finish`, req.body) + .then(response => { + axios.all([ + axios.get(`${req.app.locals.kcapp.api}/leg/${legId}`), + axios.get(`${req.app.locals.kcapp.api}/leg/${legId}/players`), + axios.get(`${req.app.locals.kcapp.api}/statistics/global/fnc`) + ]).then(axios.spread((legData, playersData, globalData) => { + const leg = legData.data; + const players = playersData.data; + const currentPlayer = _.findWhere(players, { is_current_player: true }); + const globalstat = globalData.data[0]; + + axios.get(`${req.app.locals.kcapp.api}/match/${leg.match_id}`) + .then((response) => { + const match = response.data; + + const winnerPlayer = _.findWhere(players, { player_id: leg.winner_player_id }); + //announceLegFinished(winnerPlayer, match) + + if (!match.is_finished) { + this.socketHandler.setupLegsNamespace(match.current_leg_id); + + // Forward all spectating clients to next leg + this.socketHandler.emitMessage(`/legs/${legId}`, 'new_leg', { match: match, leg: leg }); + } + this.socketHandler.emitMessage(`/active`, 'leg_finished', { match: match, leg: leg }); + this.socketHandler.emitMessage(`/legs/${legId}`, 'score_update', { leg: leg, players: players, match: match }); + this.socketHandler.emitMessage(`/legs/${legId}`,'leg_finished', { leg: leg, match: match }); + + setTimeout(() => { + // Remove the namespace in a bit, once announcements are finished + this.socketHandler.removeNamespace(legId); + }, 15000); + res.status(200).send({ leg_id: match.current_leg_id, match: match }).end(); + }).catch(error => { + const message = `${error.message}(${error})`; + debug(`[${legId}] Error when getting match: ${message}`); + next(error); + }); + })).catch(error => { + const message = `${error.message} (${error})`; + debug(`[${legId}] Error when getting leg: ${message}`); + next(error); + }); + }).catch(error => { + debug(`[${legId}] Unable to finish leg: ${error}`); + next(error); + }); +}); + + /** Method to undo leg finish */ router.put('/:id/undo', function (req, res, next) { axios.put(`${req.app.locals.kcapp.api}/leg/${req.params.id}/undo`) diff --git a/src/components/scorecard/components/x01.js b/src/components/scorecard/components/x01.js index 99d9b4f..8af3715 100644 --- a/src/components/scorecard/components/x01.js +++ b/src/components/scorecard/components/x01.js @@ -32,6 +32,15 @@ exports.isBust = (player, dart, totalScore, leg) => { return currentScore < 2; } +exports.isMaxRound = (player, dartsThrown, leg, players) => { + if (player.player_id === players[players.length - 1].player_id && + dartsThrown > 3 && + leg.parameters.max_rounds && leg.parameters.max_rounds === leg.round) { + return true; + } + return false; +} + exports.isCheckout = (player, dart, totalScore, leg) => { let currentScore = player.current_score - dart.getValue(); if (player.player.options && !player.player.options.subtract_per_dart) { @@ -65,6 +74,7 @@ exports.confirmThrow = function (external) { const isCheckout = module.exports.isCheckout(this.state.player, dart, this.state.totalScore, this.state.leg); const isBust = module.exports.isBust(this.state.player, dart, this.state.totalScore, this.state.leg); + const isMaxRound = module.exports.isMaxRound(this.state.player, this.state.currentDart, this.state.leg, this.input.players); if (isCheckout) { submitting = true; } @@ -89,6 +99,11 @@ exports.confirmThrow = function (external) { this.emit('player-busted', true); } } + else if (isMaxRound) { + alertify.notify(`Maximum numbers of rounds reached.`, 'warning'); + this.emit('max-rounds-reached', true); + } + if (!this.state.player.player.options || this.state.player.player.options.subtract_per_dart) { this.state.player.current_score -= scored; } @@ -98,4 +113,3 @@ exports.confirmThrow = function (external) { } return submitting; } - diff --git a/src/pages/index/components/new-game-form/new-game-form.component.js b/src/pages/index/components/new-game-form/new-game-form.component.js index 0778b2e..5f07e8d 100644 --- a/src/pages/index/components/new-game-form/new-game-form.component.js +++ b/src/pages/index/components/new-game-form/new-game-form.component.js @@ -22,6 +22,7 @@ module.exports = { stake: null, venue_id: null }, + maxRounds: input.max_rounds_x01, playerId: "", socket: {}, demo_mode: false, @@ -104,7 +105,7 @@ module.exports = { for (const file of data.audios) { audioPlayers.push(new Audio(file.file)); } - + for (let i = 0; i < audioPlayers.length; i++) { const current = audioPlayers[i]; const next = audioPlayers[i + 1]; @@ -314,6 +315,13 @@ module.exports = { } this.state.options.starting_score = scoreComponent.state.index + + if (this.state.options.game_type === types.X01) { + this.state.maxRounds = this.input.max_rounds_x01; + } else if (this.state.options.game_type === types.ONESEVENTY) { + this.state.maxRounds = this.input.max_rounds_170; + } + let selectedPlayers = this.getComponents('players'); for (let i = 0; i < selectedPlayers.length; i++) { selectedPlayers[i].handleTypeChange(this.state.options.game_type); diff --git a/src/pages/index/components/new-game-form/new-game-form.marko b/src/pages/index/components/new-game-form/new-game-form.marko index b58a2db..b45f51e 100644 --- a/src/pages/index/components/new-game-form/new-game-form.marko +++ b/src/pages/index/components/new-game-form/new-game-form.marko @@ -64,7 +64,11 @@ $ const types = require('../../../../components/scorecard/components/match_types
- + + + + + @@ -72,8 +76,8 @@ $ const types = require('../../../../components/scorecard/components/match_types - - + + diff --git a/src/pages/index/index-template.marko b/src/pages/index/index-template.marko index fc8fd1f..fef3e25 100644 --- a/src/pages/index/index-template.marko +++ b/src/pages/index/index-template.marko @@ -3,7 +3,9 @@ import Layout from "../layout.marko" <${Layout}> <@body> diff --git a/src/pages/leg/components/leg/leg.component.js b/src/pages/leg/components/leg/leg.component.js index 1b460c5..4f8dea6 100644 --- a/src/pages/leg/components/leg/leg.component.js +++ b/src/pages/leg/components/leg/leg.component.js @@ -205,6 +205,10 @@ module.exports = { } }, + onMaxRoundsReached(component) { + $("#pick-winner-modal").modal("show"); + }, + onUndoThrow() { if (this.state.leg.visits.length > 0) { this.state.submitting = true; diff --git a/src/pages/leg/components/leg/leg.marko b/src/pages/leg/components/leg/leg.marko index e31b5be..36efe6e 100644 --- a/src/pages/leg/components/leg/leg.marko +++ b/src/pages/leg/components/leg/leg.marko @@ -18,6 +18,7 @@ $ const types = require('../../../../components/scorecard/components/match_types on-possible-throw("onPossibleThrow") on-score-change("onScoreChange") on-player-busted("onPlayerBusted") + on-max-rounds-reached("onMaxRoundsReached") on-leg-finished("onLegFinished") on-undo-throw("onUndoThrow")/> @@ -35,6 +36,7 @@ $ const types = require('../../../../components/scorecard/components/match_types on-possible-throw("onPossibleThrow") on-score-change("onScoreChange") on-player-busted("onPlayerBusted") + on-max-rounds-reached("onMaxRoundsReached") on-leg-finished("onLegFinished") on-undo-throw("onUndoThrow")/> @@ -50,6 +52,7 @@ $ const types = require('../../../../components/scorecard/components/match_types
+ diff --git a/src/pages/leg/components/pick-winner-modal/pick-winner-modal.component.js b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.component.js new file mode 100644 index 0000000..9d508e0 --- /dev/null +++ b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.component.js @@ -0,0 +1,93 @@ +const _ = require("underscore"); +const axios = require('axios'); + +module.exports = { + onCreate(input) { + this.state = { + legId: input.legId, + players: input.players, + winner: undefined, + submitting: false + } + }, + + onMount() { + const modal = document.getElementById('pick-winner-modal'); + modal.addEventListener("keydown", this.onKeyDown.bind(this), false); + modal.addEventListener("keypress", this.onKeyPress.bind(this), false); + }, + onKeyPress(e) { + e.stopPropagation(); + }, + onKeyDown(e) { + if (e.key === 'Enter') { + if (this.state.submitting) { + e.preventDefault(); + e.stopPropagation(); + return; + } + this.confirmWinner(); + + // We don`t want to submit darts in the background + e.preventDefault(); + } else if (e.key == 'ESC') { + // Don`t allow closing this modal + e.preventDefault(); + } else { + const player = this.input.players[parseInt(e.key) - 1]; + if (player) { + const exist = _.findWhere(this.state.players, { player_id: player.player_id }); + if (exist) { + this.addPlayer(player); + } else { + this.removePlayer(player); + } + } + } + e.stopPropagation(); + }, + + playerSelected(playerId) { + const player = _.findWhere(this.input.players, { player_id: playerId }); + this.addPlayer(player); + }, + + addPlayer(player) { + this.state.players = _.reject(this.input.players, (p) => p.player_id === player.player_id); + this.setStateDirty('players'); + + this.state.winner = player; + this.setStateDirty('winner'); + }, + + removePlayer(player) { + this.state.winner = undefined; + this.setStateDirty('winner'); + + this.state.players.push(player); + this.setStateDirty('players'); + }, + + confirmWinner(event) { + this.state.submitting = true; + if (!this.state.winner) { + alert("Please select a winner"); + this.state.submitting = false; + return; + } + axios.put(`${window.location.origin}/legs/${this.state.legId}/finish`, { winner_id: this.state.winner.player_id }) + .then(response => { + const match = response.data.match; + if (match.is_finished) { + location.href = `/matches/${match.id}/result?finished=true`; + } else { + location.href = `/legs/${response.data.leg_id}`; + } + }).catch(error => { + this.state.submitting = false; + alert('Error choosing winner. Please reload'); + console.log(error); + location.reload(); + }); + } +}; \ No newline at end of file diff --git a/src/pages/leg/components/pick-winner-modal/pick-winner-modal.marko b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.marko new file mode 100644 index 0000000..7dfcd32 --- /dev/null +++ b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.marko @@ -0,0 +1,53 @@ + diff --git a/src/pages/leg/components/pick-winner-modal/pick-winner-modal.style.less b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.style.less new file mode 100644 index 0000000..ca90f44 --- /dev/null +++ b/src/pages/leg/components/pick-winner-modal/pick-winner-modal.style.less @@ -0,0 +1,16 @@ +.key-small { + padding-left: 8px; + padding-right: 8px; + height: 25px; + background-color: #eeeff1; + color: #aaaaaa; + font-weight: normal; + line-height: 25px; + border-radius: 5px; + box-shadow: 0px 1px 0px 2px #999; +} + +body { + // Opening modal adds padding for some reason + padding-right: 0px !important; +} diff --git a/src/util/socket.io-helper.js b/src/util/socket.io-helper.js index c0556ac..698ea07 100644 --- a/src/util/socket.io-helper.js +++ b/src/util/socket.io-helper.js @@ -31,6 +31,7 @@ exports.onScoreUpdate = (data, thiz) => { const scorecardComponents = thiz.getComponents('players'); let isLastVisitFishNChips = false; + let isSecondLastRound = false; let totalFishNChips = 0; const globalFish = globalstat ? globalstat.fish_n_chips : 0; const currentPlayerIdx = _.findIndex(scorecardComponents, (component) => component.state.player.player_id === leg.current_player_id); @@ -44,6 +45,8 @@ exports.onScoreUpdate = (data, thiz) => { isLastVisitFishNChips = !previousPlayer.player.is_bot && previousPlayer.modifiers.is_fish_and_chips; component.reset(); component.state.jdcDart = null; + + isSecondLastRound = player.player_id === players[0].player_id && leg.parameters.max_rounds && leg.parameters.max_rounds === leg.round; } else { if (currentPlayerIdx <= i) { component.state.jdcDart = null; @@ -65,6 +68,9 @@ exports.onScoreUpdate = (data, thiz) => { let msg = alertify.notify(getFishNChipsHTML(totalFishNChips - 1, globalFish - 1), 'fish-n-chips', 5, () => { }); setInterval(() => { msg.setContent(getFishNChipsHTML(totalFishNChips, globalFish)); }, 1000); } + if (isSecondLastRound) { + alertify.notify('Final 3 Darts per player!', 'warning'); + } } else if (thiz.state.matchType == types.TIC_TAC_TOE) { thiz.getComponent("tic-tac-toe-board").resetBoard(leg.parameters); }