Skip to content

Commit 861aa46

Browse files
authored
Roll command (#41)
* roll command roll command roll command roll command roll command roll command clean and fix * fix * test + sugggestions * fix tests
1 parent 8ba8fda commit 861aa46

File tree

6 files changed

+133
-10
lines changed

6 files changed

+133
-10
lines changed

src/commands/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('index', () => {
1515
const memberMock = {
1616
hasPermission: Sinon.spy(),
1717
};
18-
msg.guild.members.fetch.resolves(memberMock);
18+
msg.guild.fetchMember.resolves(memberMock);
1919
msg.author.send.resolves();
2020
await handleCommand((msg as unknown) as Discord.Message);
2121
expect(msg.reply).to.have.been.calledOnceWith('Wysłałam Ci DM ze wszystkimi komendami! 🎉');

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import odpowiedz from './odpowiedz';
1414
import prune from './prune';
1515
import quiz from './quiz';
1616
import regulamin from './regulamin';
17+
import roll from './roll';
1718
import server from './server';
1819
import spotify from './spotify';
1920
import xd from './xd';
@@ -36,6 +37,7 @@ const allCommands = {
3637
prune,
3738
quiz,
3839
regulamin,
40+
roll,
3941
server,
4042
spotify,
4143
xd,

src/commands/prune.spec.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,25 @@ describe('prune', () => {
3737

3838
it('should fetch messages and delete them', async () => {
3939
const msg = getMessageMock('msg');
40+
const memberMock = {
41+
hasPermission: Sinon.spy(),
42+
};
4043
// tslint:disable-next-line: no-any
4144
const messagesCollectionMock = { clear: Sinon.spy() };
42-
msg.channel.messages.fetch.resolves(messagesCollectionMock);
45+
// msg.channel.messages.fetch.resolves(messagesCollectionMock);
46+
msg.channel.fetchMessages.resolves(messagesCollectionMock);
47+
msg.guild.fetchMember.resolves(memberMock);
4348

4449
await expect(prune.execute((msg as unknown) as Discord.Message, ['2'])).to.be.fulfilled;
45-
await expect(msg.channel.messages.fetch).to.have.been.calledOnceWithExactly({ limit: 2 });
50+
await expect(msg.channel.fetchMessages).to.have.been.calledOnceWithExactly({ limit: 2 });
4651
await expect(messagesCollectionMock.clear).to.have.been.calledOnce;
4752
});
4853

4954
it('should delete itself', async () => {
5055
const msg = getMessageMock('msg');
5156
// tslint:disable-next-line: no-any
5257
const messagesCollectionMock = { clear: Sinon.spy() } as any;
53-
msg.channel.messages.fetch.resolves(messagesCollectionMock);
58+
msg.channel.fetchMessages.resolves(messagesCollectionMock);
5459

5560
await prune.execute((msg as unknown) as Discord.Message, ['2']);
5661

src/commands/roll.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/* eslint no-implicit-dependencies: "off" */
2+
/* eslint no-magic-numbers: "off" */
3+
/* tslint:disable:no-implicit-dependencies no-magic-numbers */
4+
import { expect } from 'chai';
5+
import 'mocha';
6+
7+
import { getMessageMock } from '../../test/mocks';
8+
import * as Discord from 'discord.js';
9+
10+
import roll, { parseDice, rollDices, instruction } from './roll';
11+
12+
describe('roll', () => {
13+
describe('parser', () => {
14+
it('parses valid simple roll', () => {
15+
const res = parseDice('4d6');
16+
expect(res.count).to.be.equal(4);
17+
expect(res.dice).to.be.equal(6);
18+
});
19+
it('throws on invalid input', () => {
20+
expect(() => parseDice('4ds6')).to.throw();
21+
});
22+
it('parses valid roll with modifier', () => {
23+
const res = parseDice('4d6+5');
24+
expect(res.count).to.be.equal(4);
25+
expect(res.dice).to.be.equal(6);
26+
expect(res.modifier).to.be.equal(5);
27+
});
28+
it('parses valid roll with negative modifier', () => {
29+
const res = parseDice('4d6-3');
30+
expect(res.count).to.be.equal(4);
31+
expect(res.dice).to.be.equal(6);
32+
expect(res.modifier).to.be.equal(-3);
33+
});
34+
});
35+
describe('roll', () => {
36+
it('allows valid response', () => {
37+
const count = 4;
38+
const dice = 6;
39+
const modifier = 1;
40+
const notation = `${count}d${dice}+${modifier}`;
41+
const res = rollDices(notation);
42+
expect(res.notation).to.be.equal(notation);
43+
expect(res.value).to.be.lessThan(count * dice * modifier + 1);
44+
});
45+
it('does not allow invalid response', () => {
46+
const count = 400;
47+
const dice = 6;
48+
const modifier = 1;
49+
const notation = `${count}d${dice}+${modifier}`;
50+
expect(() => rollDices(notation)).to.throw();
51+
});
52+
});
53+
describe('handleCommand', () => {
54+
it('should reply', async () => {
55+
const msg = getMessageMock('msg');
56+
await roll.execute((msg as unknown) as Discord.Message, ['4d6']);
57+
await expect(msg.channel.send).to.have.been.calledOnce;
58+
});
59+
it('reply instruction on fail', async () => {
60+
const msg = getMessageMock('msg');
61+
await roll.execute((msg as unknown) as Discord.Message, ['3k5']);
62+
await expect(msg.channel.send).to.have.been.calledOnceWith(instruction);
63+
});
64+
});
65+
});

src/commands/roll.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Command } from '../types';
2+
3+
const MAX_COUNT = 20;
4+
const MAX_DICE = 100;
5+
const REGEX = /([0-9]{1,4})d([0-9]{1,4})([+-][0-9]{1,10})?/;
6+
7+
export function parseDice(arg: string) {
8+
const res = arg.match(REGEX);
9+
if (res == null) {
10+
throw new Error('Invalid argument');
11+
}
12+
const [notation, count, dice, modifier] = res;
13+
return {
14+
notation,
15+
count: parseInt(count, 10),
16+
dice: parseInt(dice, 10),
17+
modifier: parseInt(modifier ?? 0, 10),
18+
};
19+
}
20+
21+
export function rollDices(content: string) {
22+
const parsed = parseDice(content);
23+
if (parsed.count <= 0 || parsed.count > MAX_COUNT || parsed.dice <= 0 || parsed.dice > MAX_DICE) {
24+
throw new Error('Invalid argument values');
25+
}
26+
const rolls = Array.from({ length: parsed.count })
27+
.map(() => Math.random() * parsed.dice + 1)
28+
.map(Math.floor);
29+
return {
30+
notation: parsed.notation,
31+
value: rolls.reduce((acc, val) => acc + val, parsed.modifier),
32+
rolls,
33+
};
34+
}
35+
36+
export const instruction =
37+
'Wpisz `!roll [liczba kości]d[liczba ścian]`, aby rzucić kośćmi, np `!roll 2d6+1`';
38+
39+
const roll: Command = {
40+
name: 'roll',
41+
description: 'Rzuca kośćmi.',
42+
args: true,
43+
execute(msg, args) {
44+
try {
45+
const result = rollDices(args[0]);
46+
const rollsResult = `${result.rolls.join('+')}`;
47+
const response = `:game_die: [${result.notation}] **${result.value}** = ${rollsResult}`;
48+
return msg.channel.send(response);
49+
} catch (error) {
50+
return msg.channel.send(instruction);
51+
}
52+
},
53+
};
54+
55+
export default roll;

test/mocks.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,13 @@ export const getMessageMock = (name: string, params: any = {}) => {
4040
...params,
4141
channel: {
4242
send: Sinon.stub(),
43-
messages: {
44-
fetch: Sinon.stub(),
45-
},
43+
fetchMessages: Sinon.stub(),
4644
type: '',
4745
[Symbol.toStringTag]: () => 'MOCK CHANNEL',
4846
...params.channel,
4947
},
5048
guild: {
51-
members: {
52-
fetch: Sinon.stub(),
53-
},
49+
fetchMember: Sinon.stub(),
5450
...params.guild,
5551
},
5652
delete: Sinon.stub(),

0 commit comments

Comments
 (0)