Skip to content

Commit

Permalink
GH-388: Add toggleable embed for the game board
Browse files Browse the repository at this point in the history
  • Loading branch information
utarwyn committed Jan 17, 2023
1 parent 1fad691 commit 6b7d214
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 27 deletions.
5 changes: 3 additions & 2 deletions config/config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"simultaneousGames": false,
"aiDifficulty": "Medium",
"gameExpireTime": 30,
"gameBoardReactions": false,
"gameBoardDelete": false,
"gameBoardDisableButtons": false,
"gameBoardEmbed": false,
"gameBoardEmojies": [],
"gameBoardDisableButtons": false
"gameBoardReactions": false
}
14 changes: 14 additions & 0 deletions src/__tests__/GameBoard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ describe('GameBoard', () => {
toMessageOptions: jest.fn(),
withBoard: jest.fn().mockReturnThis(),
withButtonsDisabledAfterUse: jest.fn().mockReturnThis(),
withEmbed: jest.fn().mockReturnThis(),
withEmojies: jest.fn().mockReturnThis(),
withEndingMessage: jest.fn().mockReturnThis(),
withEntityPlaying: jest.fn().mockReturnThis(),
Expand Down Expand Up @@ -108,6 +109,12 @@ describe('GameBoard', () => {
expect(mockedBuilder.withEmojies).toHaveBeenCalledWith('1', '2');
});

it('should set embed in builder if embed is enabled', () => {
configuration.gameBoardEmbed = true;
gameBoard.content;
expect(mockedBuilder.withEmbed).toHaveBeenCalledTimes(1);
});

it.each`
gameBoardDisableButtons | gameBoardReactions | calledTimes | description
${false} | ${true} | ${0} | ${'enable'}
Expand Down Expand Up @@ -197,6 +204,13 @@ describe('GameBoard', () => {
expect(ai.operate).toHaveBeenCalledTimes(2);
expect(game.nextPlayer).toHaveBeenCalledTimes(1);
});

it('should end tunnel if game board has to be deleted', async () => {
configuration.gameBoardDelete = true;
jest.spyOn(ai, 'operate').mockReturnValue({ move: 5, score: 1 });
await gameBoard.attemptNextTurn();
expect(tunnel.end).toHaveBeenCalledTimes(1);
});
});

describe('Human: wait for a reaction', () => {
Expand Down
8 changes: 7 additions & 1 deletion src/__tests__/GameBoardBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ describe('GameBoardBuilder', () => {
it('should set state based if game has expired', () => {
builder.withExpireMessage();
expect(builder.toMessageOptions()).toEqual(
expect.objectContaining({ content: 'game.expire' })
expect.objectContaining({ content: 'game.expire', embeds: undefined })
);
});

it('should use an embed if configured to use it', () => {
const options = builder.withEmbed().toMessageOptions();
expect(options.content).toBeUndefined();
expect(options.embeds).toHaveLength(1);
});
});
6 changes: 6 additions & 0 deletions src/__tests__/GameBoardButtonBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,10 @@ describe('GameBoardButtonBuilder', () => {
expect((options.components![1].components[0] as MessageButton).disabled).toBeTruthy();
expect((options.components![1].components[1] as MessageButton).disabled).toBeTruthy();
});

it('should use an embed if configured to use it', () => {
const options = builder.withEmbed().toMessageOptions();
expect(options.content).toBeUndefined();
expect(options.embeds).toHaveLength(1);
});
});
33 changes: 25 additions & 8 deletions src/bot/builder/GameBoardBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export default class GameBoardBuilder {
* @protected
*/
protected boardData: Player[];
/**
* Should enable embed to display the game board.
* @private
*/
protected embed: boolean;

/**
* Constructs a new game board builder.
Expand All @@ -50,6 +55,7 @@ export default class GameBoardBuilder {
this.state = '';
this.boardSize = 0;
this.boardData = [];
this.embed = false;
}

/**
Expand All @@ -59,7 +65,7 @@ export default class GameBoardBuilder {
* @param player2 second entity to play
* @returns same instance
*/
withTitle(player1: Entity, player2: Entity): GameBoardBuilder {
public withTitle(player1: Entity, player2: Entity): GameBoardBuilder {
this.title =
localize.__('game.title', {
player1: player1.displayName,
Expand All @@ -75,7 +81,7 @@ export default class GameBoardBuilder {
* @param second emoji of the second entity
* @returns same instance
*/
withEmojies(first: string, second: string): GameBoardBuilder {
public withEmojies(first: string, second: string): GameBoardBuilder {
this.emojies[1] = first;
this.emojies[2] = second;
return this;
Expand All @@ -88,7 +94,7 @@ export default class GameBoardBuilder {
* @param board game board data
* @returns same instance
*/
withBoard(boardSize: number, board: Player[]): GameBoardBuilder {
public withBoard(boardSize: number, board: Player[]): GameBoardBuilder {
this.boardSize = boardSize;
this.boardData = board;
return this;
Expand All @@ -100,7 +106,7 @@ export default class GameBoardBuilder {
* @param entity entity whiches is playing. If undefined: display loading message
* @returns same instance
*/
withEntityPlaying(entity?: Entity): GameBoardBuilder {
public withEntityPlaying(entity?: Entity): GameBoardBuilder {
if (entity instanceof AI) {
this.state = localize.__('game.waiting-ai');
} else if (!entity) {
Expand All @@ -117,7 +123,7 @@ export default class GameBoardBuilder {
* @param winner winning entity. If undefined: display tie message
* @returns same instance
*/
withEndingMessage(winner?: Entity): GameBoardBuilder {
public withEndingMessage(winner?: Entity): GameBoardBuilder {
if (winner) {
this.state = localize.__('game.win', { player: winner.toString() });
} else {
Expand All @@ -131,17 +137,27 @@ export default class GameBoardBuilder {
*
* @returns same instance
*/
withExpireMessage(): GameBoardBuilder {
public withExpireMessage(): GameBoardBuilder {
this.state = localize.__('game.expire');
return this;
}

/**
* Should use an embed to display the game board.
*
* @returns same instance
*/
public withEmbed(): GameBoardBuilder {
this.embed = true;
return this;
}

/**
* Constructs final representation of the game board.
*
* @returns message options of the gameboard
*/
toMessageOptions(): MessageOptions {
public toMessageOptions(): MessageOptions {
// Generate string representation of the board
let board = '';

Expand All @@ -156,7 +172,8 @@ export default class GameBoardBuilder {
const state = this.state && board ? '\n' + this.state : this.state;
return {
allowedMentions: { parse: ['users'] },
content: this.title + board + state,
embeds: this.embed ? [{ title: this.title, description: board + state }] : undefined,
content: !this.embed ? this.title + board + state : undefined,
components: []
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/bot/builder/GameBoardButtonBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ export default class GameBoardButtonBuilder extends GameBoardBuilder {
*/
override toMessageOptions(): MessageOptions {
return {
content: this.title + this.state,
embeds: this.embed ? [{ title: this.title, description: this.state }] : undefined,
content: !this.embed ? this.title + this.state : undefined,
components: [...Array(this.boardSize).keys()].map(row =>
new MessageActionRow().addComponents(
[...Array(this.boardSize).keys()].map(col => this.createButton(row, col))
Expand Down
28 changes: 21 additions & 7 deletions src/bot/entity/GameBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ export default class GameBoard {
* Creates or retrieves message of the gameboard.
*/
public get content(): WebhookEditMessageOptions {
const builder = this.configuration.gameBoardReactions
? new GameBoardBuilder()
: new GameBoardButtonBuilder();
const builder = this.createBuilder();

builder
.withTitle(this.entities[0], this.entities[1])
Expand Down Expand Up @@ -239,9 +237,8 @@ export default class GameBoard {
const winner = this.getEntity(this.game.winner);

if (this.configuration.gameBoardDelete) {
await this.tunnel.end(
new GameBoardBuilder().withEndingMessage(winner).toMessageOptions()
);
const options = this.createBuilder().withEndingMessage(winner).toMessageOptions();
await this.tunnel.end(options);
} else {
await this.tunnel.reply?.reactions?.removeAll();
await this.update(interaction);
Expand All @@ -260,10 +257,27 @@ export default class GameBoard {
* @private
*/
private async onExpire(): Promise<void> {
await this.tunnel.end(new GameBoardBuilder().withExpireMessage().toMessageOptions());
await this.tunnel.end(this.createBuilder().withExpireMessage().toMessageOptions());
this.manager.endGame(this);
}

/**
* Creates a builder based on the game configuration.
*
* @returns game board builder
*/
private createBuilder(): GameBoardBuilder {
const builder = this.configuration.gameBoardReactions
? new GameBoardBuilder()
: new GameBoardButtonBuilder();

if (this.configuration.gameBoardEmbed) {
builder.withEmbed();
}

return builder;
}

/**
* Waits for the current player to select a move with one reaction below the message.
* @private
Expand Down
5 changes: 3 additions & 2 deletions src/config/ConfigProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ export default class ConfigProvider implements Config {

public aiDifficulty: AIDifficulty = 'Medium';
public gameExpireTime = 30;
public gameBoardReactions = false;
public gameBoardDelete = false;
public gameBoardEmojies = [];
public gameBoardDisableButtons = false;
public gameBoardEmbed = false;
public gameBoardEmojies = [];
public gameBoardReactions = false;

[key: string]: any;

Expand Down
16 changes: 10 additions & 6 deletions src/config/GameConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,24 @@ export default interface GameConfig {
* Expiration time of a player turn.
*/
gameExpireTime?: number;
/**
* Interact with gameboard using reactions instead of buttons.
*/
gameBoardReactions?: boolean;
/**
* Should bot needs to delete the game board message.
*/
gameBoardDelete?: boolean;
/**
* Should disable buttons after been used.
*/
gameBoardDisableButtons?: boolean;
/**
* Should use an embed to display the game board.
*/
gameBoardEmbed?: boolean;
/**
* List of emojies used to identify players.
*/
gameBoardEmojies?: string[];
/**
* Should disable buttons after been used.
* Interact with game board using reactions instead of buttons.
*/
gameBoardDisableButtons?: boolean;
gameBoardReactions?: boolean;
}

0 comments on commit 6b7d214

Please sign in to comment.