case 'solve':
if ($channel->hasActiveGuessing() && $channel->isValidGuess($validatedData['solution'])) {
$winners = $channel->solveGuessing($validatedData['solution']);
- $names = [];
- foreach ($winners as $winner) {
- if ($winner->score > 0) {
- $names[] = $winner->uname;
- }
- }
- if (empty($names)) {
- $msg = $channel->getGuessingSetting('no_winners_message');
- } else {
- $msg = $channel->getGuessingSetting('winners_message');
- $msg = str_replace('{names}', $channel->listAnd($names), $msg);
- }
+ $msg = $channel->listGuessingWinners($winners);
if (!empty($msg)) {
TwitchBotCommand::chat($channel->twitch_chat, $msg);
}
$validatedData = $request->validate([
'active_message' => 'string',
'cancel_message' => 'string',
+ 'close_winners_message' => 'string',
'invalid_solution_message' => 'string',
+ 'leaderboard_type' => 'string',
'no_winners_message' => 'string',
'not_active_message' => 'string',
'points_exact_first' => 'numeric|min:1|max:5',
}
public function getGuessingLeaderboard() {
- return $this->winners()->selectRaw('(select t2.uname from guessing_winners t2 where t2.uid = guessing_winners.uid order by created_at desc limit 1) as name, sum(score) as score')->groupBy('uid')->orderBy('score', 'desc')->limit(10)->get();
+ $query = $this->winners()
+ ->selectRaw('(select t2.uname from guessing_winners t2 where t2.uid = guessing_winners.uid order by created_at desc limit 1) as name, sum(score) as score')
+ ->groupBy('uid')
+ ->orderBy('score', 'desc')
+ ->limit(10);
+ $type = $this->getGuessingSetting('leaderboard_type', 'all');
+ if ($type == 'month') {
+ $query->where('created_at', '>=', now()->startOfMonth());
+ } else if ($type == 'year') {
+ $query->where('created_at', '>=', now()->startOfYear());
+ } else if (is_numeric($type)) {
+ $query->where('created_at', '>=', now()->sub($type, 'days'));
+ }
+ return $query->get();
}
public function hasActiveGuessing() {
$this->save();
}
+ public function transformGuess($original) {
+ $transformed = trim($original);
+ if ($this->guessing_type == 'gtbk') {
+ $transformed = str_replace(['roodyo1Gtbigkey'], ['2'], $transformed);
+ $transformed = str_ireplace(['vier 4Head'], ['4'], $transformed);
+ }
+ return $transformed;
+ }
+
public function registerGuess($uid, $uname, $guess) {
$model = new GuessingGuess();
$model->channel()->associate($this);
$model->uid = $uid;
$model->uname = $uname;
- $model->guess = $guess;
+ $model->guess = $this->transformGuess($guess);
$model->save();
}
public function scoreGuessing($solution, $guess, $first) {
- if ($guess == $solution) {
+ $transformed = $this->transformGuess($guess);
+ if ($transformed == $solution) {
if ($first) {
return $this->getGuessingSetting('points_exact_first', 1);
}
return $this->getGuessingSetting('points_exact_other', 1);
}
- $distance = abs(intval($guess) - intval($solution));
+ $distance = abs(intval($transformed) - intval($solution));
if ($distance <= $this->getGuessingSetting('points_close_max', 3)) {
if ($first) {
return $this->getGuessingSetting('points_close_first', 1);
}
public function isValidGuess($solution) {
+ $transformed = $this->transformGuess($solution);
if ($this->guessing_type == 'gtbk') {
- $int_solution = intval($solution);
- return $int_solution > 0 && $int_solution < 23;
+ $int_solution = intval($transformed);
+ return is_numeric($transformed) && $int_solution > 0 && $int_solution < 23;
}
return false;
}
+ public function listGuessingWinners($winners) {
+ $names = [];
+ $distance = 0;
+ foreach ($winners as $winner) {
+ if ($winner->score > 0) {
+ $names[] = $winner->uname;
+ $distance = abs(intval($winner->guess) - intval($winner->solution));
+ }
+ }
+ $msg = '';
+ if (empty($names)) {
+ $msg = $this->getGuessingSetting('no_winners_message');
+ } else {
+ $msg = $this->getGuessingSetting($distance ? 'close_winners_message' : 'winners_message', $this->getGuessingSetting('winners_message'));
+ $msg = str_replace(['{distance}', '{names}'], [$distance, $this->listAnd($names)], $msg);
+ }
+ return $msg;
+ }
+
public function listAnd($entries) {
$lang = empty($this->languages) ? 'en' : $this->languages[0];
if ($lang == 'de') {
return;
}
$winners = $this->channel->solveGuessing($args);
- $names = [];
- foreach ($winners as $winner) {
- if ($winner->score > 0) {
- $names[] = $winner->uname;
- }
- }
- if (empty($names)) {
- $msg = $this->channel->getGuessingSetting('no_winners_message');
- $this->messageChannel($msg);
- } else {
- $msg = $this->channel->getGuessingSetting('winners_message');
- $msg = str_replace('{names}', $this->listAnd($names), $msg);
- $this->messageChannel($msg);
- }
+ $msg = $this->channel->listGuessingWinners($winners);
+ $this->messageChannel($msg);
$this->channel->clearGuessing();
}
</Form.Control.Feedback>
: null}
</Form.Group>
+ <Form.Group as={Col} controlId="gg.leaderboard_type" md={6}>
+ <Form.Label>{t('twitchBot.guessingGame.leaderboardType')}</Form.Label>
+ <Form.Select
+ isInvalid={!!(touched.leaderboard_type && errors.leaderboard_type)}
+ name="leaderboard_type"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ value={values.leaderboard_type || 'all'}
+ >
+ {['all', 'year', '365', 'month', '30'].map(type =>
+ <option key={type} value={type}>
+ {t(`twitchBot.guessingGame.leaderboardTypes.${type}`)}
+ </option>
+ )}
+ </Form.Select>
+ {touched.leaderboard_type && errors.leaderboard_type ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.leaderboard_type)}
+ </Form.Control.Feedback>
+ : null}
+ </Form.Group>
</Row>
<Form.Group controlId="gg.start_message">
<Form.Label>{t('twitchBot.guessingGame.startMessage')}</Form.Label>
</Form.Text>
}
</Form.Group>
+ <Form.Group controlId="gg.close_winners_message">
+ <Form.Label>{t('twitchBot.guessingGame.closeWinnersMessage')}</Form.Label>
+ <Form.Control
+ isInvalid={!!(touched.close_winners_message && errors.close_winners_message)}
+ name="close_winners_message"
+ onBlur={handleBlur}
+ onChange={handleChange}
+ type="text"
+ value={values.close_winners_message || ''}
+ />
+ {touched.close_winners_message && errors.close_winners_message ?
+ <Form.Control.Feedback type="invalid">
+ {t(errors.close_winners_message)}
+ </Form.Control.Feedback>
+ :
+ <Form.Text muted>
+ {t('twitchBot.guessingGame.closeWinnersMessageHint')}
+ </Form.Text>
+ }
+ </Form.Group>
<Form.Group controlId="gg.no_winners_message">
<Form.Label>{t('twitchBot.guessingGame.noWinnersMessage')}</Form.Label>
<Form.Control
errors: PropTypes.shape({
active_message: PropTypes.string,
cancel_message: PropTypes.string,
+ close_winners_message: PropTypes.string,
invalid_solution_message: PropTypes.string,
+ leaderboard_type: PropTypes.string,
name: PropTypes.string,
no_winners_message: PropTypes.string,
not_active_message: PropTypes.string,
touched: PropTypes.shape({
active_message: PropTypes.bool,
cancel_message: PropTypes.bool,
+ close_winners_message: PropTypes.bool,
invalid_solution_message: PropTypes.bool,
+ leaderboard_type: PropTypes.bool,
name: PropTypes.bool,
no_winners_message: PropTypes.bool,
not_active_message: PropTypes.bool,
values: PropTypes.shape({
active_message: PropTypes.string,
cancel_message: PropTypes.string,
+ close_winners_message: PropTypes.string,
invalid_solution_message: PropTypes.string,
+ leaderboard_type: PropTypes.string,
name: PropTypes.string,
no_winners_message: PropTypes.string,
not_active_message: PropTypes.string,
return {
active_message: getStringValue(settings, 'active_message', 'ActiveMessage'),
cancel_message: getStringValue(settings, 'cancel_message', 'CancelMessage'),
+ close_winners_message:
+ getStringValue(settings, 'close_winners_message', 'CloseWinnersMessage'),
invalid_solution_message:
getStringValue(settings, 'invalid_solution_message', 'InvalidSolutionMessage'),
+ leaderboard_type: (settings && settings.leaderboard_type) || 'all',
name: name || '',
no_winners_message: getStringValue(settings, 'no_winners_message', 'NoWinnersMessage'),
not_active_message: getStringValue(settings, 'not_active_message', 'NotActiveMessage'),
validationSchema: yup.object().shape({
active_message: yup.string(),
cancel_message: yup.string(),
+ close_winners_message: yup.string(),
+ leaderboard_type: yup.string(),
invalid_solution_message: yup.string(),
name: yup.string().required(),
no_winners_message: yup.string(),
guessingGame: {
activeMessage: 'Nachricht bei bereits laufendem Spiel',
cancelMessage: 'Nachricht bei Spielabbruch',
+ closeWinnersMessage: 'Nachricht bei nicht-exakten Gewinnern',
+ closeWinnersMessageHint: '{names} wird durch die Namen der Gewinner und {distance} durch den Abstand ersetzt',
defaultActiveMessage: 'Es läuft bereits ein Spiel auf diesem Kanal',
defaultCancelMessage: 'Spiel abgebrochen',
+ defaultCloseWinnersMessage: 'Glückwunsch {names}! Nur {distance} daneben!',
defaultInvalidSolutionMessage: 'Bitte eine gültige Lösung für das Guessing Game angeben',
defaultNoWinnersMessage: 'keiner gewonnen :(',
defaultNotActiveMessage: 'Es läuft gerade kein Spiel auf diesem Kanal',
guesses: 'Tipps',
guessTimestamp: '{{ timestamp, LT }}',
invalidSolutionMessage: 'Nachricht bei ungültiger (oder fehlender) Lösung',
+ leaderboardType: 'Leaderboard Style',
+ leaderboardTypes: {
+ 30: 'Laufender Monat',
+ 365: 'Laufendes Jahr',
+ all: 'All Time',
+ month: 'Aktueller Monat',
+ year: 'Aktuelles Jahr',
+ },
noWinnersMessage: 'Nachricht, falls keine Gewinner',
notActiveMessage: 'Nachricht, wenn kein Spiel läuft',
pointsCloseFirst: 'Punkte für den ersten nächsten Treffer',
guessingGame: {
activeMessage: 'Message when a game is already running',
cancelMessage: 'Game cancellation announcement',
+ closeWinnersMessage: 'Message for inexact winners',
+ closeWinnersMessageHint: '{names} will be replaced with a list of winners\' names and {distance} with the distance',
defaultActiveMessage: 'Channel already has an active guessing game',
defaultCancelMessage: 'Guessing game cancelled',
+ defaultCloseWinnersMessage: 'Congrats {names} on guessing within {distance}!',
defaultInvalidSolutionMessage: 'Please provide a valid solution to the guessing game',
defaultNoWinnersMessage: 'nobody wins :(',
defaultNotActiveMessage: 'Channel has no active guessing game',
guesses: 'Guesses',
guessTimestamp: '{{ timestamp, LT }}',
invalidSolutionMessage: 'Message for invalid (or missing) solution',
+ leaderboardType: 'Leaderboard type',
+ leaderboardTypes: {
+ 30: 'Running month',
+ 365: 'Running year',
+ all: 'All time',
+ month: 'Current month',
+ year: 'Current year',
+ },
noWinnersMessage: 'Announcement for no winners',
notActiveMessage: 'Message when no game is currently active',
pointsCloseFirst: 'Points for first close match',
--- /dev/null
+<?php
+
+namespace Tests\Unit\Models;
+
+use App\Models\Channel;
+use App\Models\GuessingWinner;
+use PHPUnit\Framework\TestCase;
+
+class ChannelTest extends TestCase {
+
+ public function test_guessing_solutions() {
+ $channel = new Channel();
+ $channel->guessing_type = 'gtbk';
+
+ $this->assertTrue($channel->isValidGuess('1'));
+ $this->assertTrue($channel->isValidGuess('22'));
+ $this->assertTrue($channel->isValidGuess('3 '));
+ $this->assertTrue($channel->isValidGuess('vier 4Head'));
+ $this->assertTrue($channel->isValidGuess('Vier 4Head'));
+ $this->assertTrue($channel->isValidGuess('roodyo1Gtbigkey'));
+
+ $this->assertFalse($channel->isValidGuess('1:22:56'));
+ $this->assertFalse($channel->isValidGuess('torch'));
+ }
+
+ public function test_guessing_scoring() {
+ $channel = new Channel();
+ $channel->guessing_type = 'gtbk';
+ $channel->guessing_settings = [
+ 'gtbk' => [
+ 'points_exact_first' => 5,
+ 'points_exact_other' => 4,
+ 'points_close_first' => 3,
+ 'points_close_other' => 2,
+ 'points_close_max' => 5,
+ ],
+ ];
+
+ $this->assertEquals(5, $channel->scoreGuessing('3', '3', true));
+ $this->assertEquals(4, $channel->scoreGuessing('12', '12', false));
+ $this->assertEquals(3, $channel->scoreGuessing('15', '14', true));
+ $this->assertEquals(2, $channel->scoreGuessing('8', '6', false));
+ $this->assertEquals(0, $channel->scoreGuessing('7', '1', true));
+ $this->assertEquals(0, $channel->scoreGuessing('3', '22', false));
+
+ $this->assertEquals(5, $channel->scoreGuessing('2', 'roodyo1Gtbigkey', true));
+ $this->assertEquals(5, $channel->scoreGuessing('4', 'Vier 4Head', true));
+ $this->assertEquals(5, $channel->scoreGuessing('5', '5 ', true));
+ }
+
+ public function test_guessing_winners() {
+ $channel = new Channel();
+ $channel->guessing_type = 'gtbk';
+ $channel->guessing_settings = [
+ 'gtbk' => [
+ 'close_winners_message' => 'within {distance}: {names}',
+ 'no_winners_message' => 'no winners',
+ 'winners_message' => 'winners: {names}',
+ ],
+ ];
+
+ $this->assertEquals('no winners', $channel->listGuessingWinners([]));
+
+ $winners = [];
+ $winner = new GuessingWinner();
+ $winner->uname = 'Horstie';
+ $winner->guess = '3';
+ $winner->solution = '3';
+ $winner->score = 1;
+ $winners[] = $winner;
+
+ $this->assertEquals('winners: Horstie', $channel->listGuessingWinners($winners));
+
+ $winner = new GuessingWinner();
+ $winner->uname = 'Borstie';
+ $winner->guess = '3';
+ $winner->solution = '3';
+ $winner->score = 1;
+ $winners[] = $winner;
+
+ $channel->languages = ['de'];
+ $this->assertEquals('winners: Horstie und Borstie', $channel->listGuessingWinners($winners));
+ $channel->languages = ['en'];
+ $this->assertEquals('winners: Horstie and Borstie', $channel->listGuessingWinners($winners));
+
+ $winners[0]->guess = '1';
+ $winners[1]->guess = '5';
+
+
+ $channel->languages = ['de'];
+ $this->assertEquals('within 2: Horstie und Borstie', $channel->listGuessingWinners($winners));
+ $channel->languages = ['en'];
+ $this->assertEquals('within 2: Horstie and Borstie', $channel->listGuessingWinners($winners));
+
+ $channel->guessing_settings = [
+ 'gtbk' => [
+ 'no_winners_message' => 'no winners',
+ 'winners_message' => 'winners: {names}',
+ ],
+ ];
+
+ $channel->languages = ['de'];
+ $this->assertEquals('winners: Horstie und Borstie', $channel->listGuessingWinners($winners));
+ $channel->languages = ['en'];
+ $this->assertEquals('winners: Horstie and Borstie', $channel->listGuessingWinners($winners));
+ }
+
+}