Skip to content

Commit

Permalink
GH-204: Send a message when a game is in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
utarwyn committed Aug 30, 2022
1 parent e165c88 commit 81f2537
Show file tree
Hide file tree
Showing 19 changed files with 93 additions and 14 deletions.
2 changes: 2 additions & 0 deletions config/locales/ar.json
Expand Up @@ -23,6 +23,8 @@
"end": "لم يفز أي أحد في هذه الجولة, هل تريدون لعب مرة اخرى؟",
"win": ":tada: فاز {player} في هذه الجولة",
"expire": ":x: **انتهت** اللعبة ... بسبب عدم تفاعل",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: الروبوت يلعب, انتضر رجاءً...",
"ai": "الروبوت"
}
}
1 change: 1 addition & 0 deletions config/locales/de.json
Expand Up @@ -23,6 +23,7 @@
"end": "Kein Sieger, unentschieden! Nochmal?",
"win": ":tada: {player} hat gewonnen!",
"expire": ":x: Spiel ist **abgelaufen**... da war wohl keiner mehr da.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: KI ist am Zug, bitte warten...",
"ai": "KI"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.json
Expand Up @@ -23,6 +23,7 @@
"end": "No one won the game, it's a tie! Let's try again?",
"win": ":tada: {player} has won the game!",
"expire": ":x: Game has **expired**... maybe because of one user inactivity.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: AI is playing, please wait...",
"ai": "the AI"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/es.json
Expand Up @@ -23,6 +23,7 @@
"end": "Nadie ganó el juego, ¡es un empate! ¿Intentemoslo de nuevo?",
"win": ":tada: {player} ha ganado el juego!",
"expire": ":x: El juego ha **caducado**... quizás debido a la inactividad de un usuario.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: La IA está jugando, por favor espere...",
"ai": "La IA"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/fr.json
Expand Up @@ -23,6 +23,7 @@
"end": "Personne n'a gagné la partie, c'est une égalité ! On réessaie ?",
"win": ":tada: {player} a gagné la partie !",
"expire": ":x: La partie vient **d'expirer**... peut-être à cause de l'inactivité d'un joueur.",
"in-progress": "vous ne pouvez pas démarrer de partie, attendez que celle en cours se termine.",
"waiting-ai": ":robot: L'IA joue, merci de patienter...",
"ai": "l'IA"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/id.json
Expand Up @@ -23,6 +23,7 @@
"end": "Tidak ada yang menang, Ini seri! Coba lagi?",
"win": ":tada: {player} Memenangkan permainan",
"expire": ":x: Game telah **Kadaluarsa**... Mungkin salah satu pemain tidak aktif",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: AI Sedang bermain, tunggu sebentar...",
"ai": "sang AI"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/it.json
Expand Up @@ -23,6 +23,7 @@
"end": "Che peccato, non ha vinto nessuno! Vuoi rigiocare?",
"win": ":tada: {player} ha vinto!",
"expire": ":x: Il tempo è **scaduto**... forse perché i giocatori non sono più online.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: L'IA sta' giocando, aspetta il tuo turno...",
"ai": "l'IA"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/nl.json
Expand Up @@ -23,6 +23,7 @@
"end": "Gelijk spel! Niemand heeft gewonnen! Nog een keer?",
"win": ":tada: {player} heeft gewonnen!",
"expire": ":x: Het spel is **verlopen**... Misschien is iemand niet actief?",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: De AI is aan het spelen, eventjes geduld...",
"ai": "AI"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/pl.json
Expand Up @@ -23,6 +23,7 @@
"end": "Remis! Nikt nie wygrał. Może spróbujcie jeszcze raz?",
"win": ":tada: {player} wygrał grę w kółko i krzyżyk!",
"expire": ":x: Gra **wygasła**... Może przez nieaktywność któregoś z graczy?",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: Trwa ruch Sztucznej Inteligencji, proszę czekać...",
"ai": "Sztuczna Inteligencja"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/pt-br.json
Expand Up @@ -23,6 +23,7 @@
"end": "Ninguém ganhou o jogo, empate! Quer tentar novamente?",
"win": ":tada: {player} venceu a partida!",
"expire": ":x: O jogo foi **expirado**... Talvez por causa de inatividade de um usuário.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: AI está jogando, espere um momento...",
"ai": "a AI"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/ru.json
Expand Up @@ -23,6 +23,7 @@
"end": "Ничья! Может быть снова попробуем?)",
"win": ":tada:{player} Выиграл игру!",
"expire": ":x: Игра была досрочно завершена... Может потому что никто не активил?",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: Компьютер думает, подождите...",
"ai": "Кампудахтр"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/tr.json
Expand Up @@ -23,6 +23,7 @@
"end": "Maçı kimse kazanamadı, berabere kaldı!",
"win": ":tada: {player} oyunu kazandı!",
"expire": ":x: Oyunun süresi **doldu**... Bir kullanıcının hareketsizliğinden olabilir",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: Yapay Zeka oynuyor, lütfen bekle...",
"ai": "Yapay Zeka"
}
Expand Down
1 change: 1 addition & 0 deletions config/locales/vi.json
Expand Up @@ -23,6 +23,7 @@
"end": "Không ai thắng màn thách đấu này. Thử lại chứ?",
"win": ":tada: {player} đã thắng ván đấu!",
"expire": ":x: Ván đấu đã **hết giờ**... Bởi vì một người đã không tương tác.",
"in-progress": "you cannot start another game, please wait for current one to end.",
"waiting-ai": ":robot: Máy đang chơi, chờ chút nha...",
"ai": "Máy"
}
Expand Down
43 changes: 41 additions & 2 deletions src/__tests__/GameStateManager.test.ts
Expand Up @@ -5,6 +5,7 @@ import MessagingTunnel from '@bot/messaging/MessagingTunnel';
import GameStateManager from '@bot/state/GameStateManager';
import GameStateValidator from '@bot/state/GameStateValidator';
import TicTacToeBot from '@bot/TicTacToeBot';
import AI from '@tictactoe/AI';
import Entity from '@tictactoe/Entity';
import { GuildMember } from 'discord.js';

Expand Down Expand Up @@ -46,11 +47,21 @@ describe('GameStateManager', () => {
const invited = <GuildMember>{};
await manager.requestDuel(tunnel, invited);
expect(spyValidate).toHaveBeenCalledTimes(1);
expect(spyValidate).toHaveBeenCalledWith(tunnel);
});

it('should check if a new game is possible', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
const spyValidate = jest.spyOn(validator, 'isNewGamePossible');
const invited = <GuildMember>{};
await manager.requestDuel(tunnel, invited);
expect(spyValidate).toHaveBeenCalledTimes(1);
expect(spyValidate).toHaveBeenCalledWith(tunnel, invited);
});

it('should create a duel request and send it into the messaging tunnel', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
jest.spyOn(validator, 'isNewGamePossible').mockReturnValue(true);
const spyReplyWith = jest.spyOn(tunnel, 'replyWith');
await manager.requestDuel(tunnel, <GuildMember>{});
expect(duelRequest).toHaveBeenCalledTimes(1);
Expand All @@ -59,6 +70,7 @@ describe('GameStateManager', () => {

it('should setup user cooldown if enabled in configuration', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
jest.spyOn(validator, 'isNewGamePossible').mockReturnValue(true);

// by default, no cooldown
await manager.requestDuel(tunnel, <GuildMember>{});
Expand All @@ -77,19 +89,46 @@ describe('GameStateManager', () => {
const spyValidate = jest.spyOn(validator, 'isInteractionValid');
await manager.createGame(tunnel);
expect(spyValidate).toHaveBeenCalledTimes(1);
expect(spyValidate).toHaveBeenCalledWith(tunnel, undefined);
expect(spyValidate).toHaveBeenCalledWith(tunnel);
});

it('should check if a new game is possible', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
const spyValidate = jest.spyOn(validator, 'isNewGamePossible');
const invited = <GuildMember>{};
await manager.createGame(tunnel, invited);
expect(spyValidate).toHaveBeenCalledTimes(1);
expect(spyValidate).toHaveBeenCalledWith(tunnel, invited);
});

it('should create a game board and send it into the messaging tunnel', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
jest.spyOn(validator, 'isNewGamePossible').mockReturnValue(true);
const spyReplyWith = jest.spyOn(tunnel, 'replyWith');

await manager.createGame(tunnel);
const invited = <GuildMember>{};
await manager.createGame(tunnel, invited);

expect(manager.gameboards).toHaveLength(1);
expect(gameBoard).toHaveBeenCalledTimes(1);
expect(gameBoard).toHaveBeenCalledWith(manager, tunnel, invited, expect.anything());
expect(spyReplyWith).toHaveBeenCalledTimes(1);
});

it('should create a game board with AI if no invited member', async () => {
jest.spyOn(validator, 'isInteractionValid').mockReturnValue(true);
jest.spyOn(validator, 'isNewGamePossible').mockReturnValue(true);

await manager.createGame(tunnel);

expect(gameBoard).toHaveBeenCalledTimes(1);
expect(gameBoard).toHaveBeenCalledWith(
manager,
tunnel,
expect.any(AI),
expect.anything()
);
});
});

describe('Method: endGame', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/GameStateValidator.test.ts
Expand Up @@ -136,7 +136,7 @@ describe('GameStateValidator', () => {
}
manager.bot.configuration.simultaneousGames = simultaneousGames;
const invited = sameInvited ? tunnel.author : undefined;
expect(validator.isInteractionValid(tunnel, invited)).toBe(expected);
expect(validator.isNewGamePossible(tunnel, invited)).toBe(expected);
}
);
});
8 changes: 6 additions & 2 deletions src/bot/command/GameCommand.ts
Expand Up @@ -100,15 +100,19 @@ export default class GameCommand {
inviter.user.id !== invited.user.id &&
invited.permissionsIn(tunnel.channel).has('VIEW_CHANNEL')
) {
await this.manager.requestDuel(tunnel, invited);
if (!(await this.manager.requestDuel(tunnel, invited))) {
await tunnel.replyWith({ content: localize.__('game.in-progress') }, true);
}
} else {
await tunnel.replyWith({ content: localize.__('duel.unknown-user') }, true);
}
} else {
await tunnel.replyWith({ content: localize.__('duel.no-bot') }, true);
}
} else {
await this.manager.createGame(tunnel);
if (!(await this.manager.createGame(tunnel))) {
await tunnel.replyWith({ content: localize.__('game.in-progress') }, true);
}
}
}
}
2 changes: 1 addition & 1 deletion src/bot/entity/DuelRequest.ts
Expand Up @@ -182,7 +182,7 @@ export default class DuelRequest {
private async challengeAnswered(accepted: boolean): Promise<void> {
if (accepted) {
await this.tunnel.end();
return this.manager.createGame(this.tunnel, this.invited);
await this.manager.createGame(this.tunnel, this.invited);
} else {
return this.tunnel.end({
allowedMentions: { parse: [] },
Expand Down
22 changes: 18 additions & 4 deletions src/bot/state/GameStateManager.ts
Expand Up @@ -49,9 +49,14 @@ export default class GameStateManager {
*
* @param tunnel messaging tunnel that initiated the request
* @param invited member invited to be part of the duel
* @returns true if duel request has been handled, false otherwise
*/
public async requestDuel(tunnel: MessagingTunnel, invited: GuildMember): Promise<void> {
if (this.validator.isInteractionValid(tunnel, invited)) {
public async requestDuel(tunnel: MessagingTunnel, invited: GuildMember): Promise<boolean> {
if (this.validator.isInteractionValid(tunnel)) {
if (!this.validator.isNewGamePossible(tunnel, invited)) {
return false;
}

const duel = new DuelRequest(
this,
tunnel,
Expand All @@ -70,16 +75,23 @@ export default class GameStateManager {
this.memberCooldownEndTimes.set(tunnel.author.id, Date.now() + cooldown * 1000);
}
}

return true;
}

/**
* Creates a game with one member and another or an AI.
*
* @param tunnel messaging tunnel that initiated the game creation
* @param invited member invited to be part of the game, undefined means the AI
* @returns true if game request has been handled, false otherwise
*/
public async createGame(tunnel: MessagingTunnel, invited?: GuildMember): Promise<void> {
if (this.validator.isInteractionValid(tunnel, invited)) {
public async createGame(tunnel: MessagingTunnel, invited?: GuildMember): Promise<boolean> {
if (this.validator.isInteractionValid(tunnel)) {
if (!this.validator.isNewGamePossible(tunnel, invited)) {
return false;
}

const gameboard = new GameBoard(
this,
tunnel,
Expand All @@ -94,6 +106,8 @@ export default class GameStateManager {
const message = await tunnel.replyWith(gameboard.content);
await gameboard.attachTo(message);
}

return true;
}

/**
Expand Down
16 changes: 12 additions & 4 deletions src/bot/state/GameStateValidator.ts
Expand Up @@ -55,13 +55,21 @@ export default class GameStateValidator {
* Checks if an interaction through a messaging tunnel is valid or not.
*
* @param tunnel messaging tunnel object
* @param invited invited guild member, can be undefined
* @returns true if the interaction is valid, false otherwise
*/
public isInteractionValid(tunnel: MessagingTunnel, invited?: GuildMember): boolean {
public isInteractionValid(tunnel: MessagingTunnel): boolean {
return this.isMessagingAllowed(tunnel) && this.isMemberAllowed(tunnel.author);
}

/**
* Checks if creating a new game is possible based on channel state and author.
*
* @param tunnel messaging tunnel object
* @param invited invited guild member, can be undefined
* @returns true if a game can be created, false otherwise
*/
public isNewGamePossible(tunnel: MessagingTunnel, invited?: GuildMember): boolean {
return (
this.isMessagingAllowed(tunnel) &&
this.isMemberAllowed(tunnel.author) &&
// Check if one of both entites is already playing
!this.manager.gameboards.some(gameboard =>
[tunnel.author, invited].some(
Expand Down

0 comments on commit 81f2537

Please sign in to comment.