pokemon-showdown/server/chat-plugins/blackjack.ts

778 lines
27 KiB
TypeScript

/**
* Blackjack Game
* Pokemon Showdown - http://pokemonshowdown.com/
*
* This allows users to play the classic blackjack card game.
* Credits: jd, panpawn
*
* @license MIT license
*/
import {Utils} from '../../lib/utils';
type Deck =
'A♥' | 'A♦' | 'A♣' | 'A♠' | '2♥' | '2♦' | '2♣' | '2♠' | '3♥' | '3♦' | '3♣' | '3♠' | '4♥' | '4♦' | '4♣' |
'4♠' | '5♥' | '5♦' | '5♣' | '5♠' | '6♥' | '6♦' | '6♣' | '6♠' | '7♥' | '7♦' | '7♣' | '7♠' | '8♥' | '8♦' |
'8♣' | '8♠' | '9♥' | '9♦' | '9♣' | '9♠' | '10♥' | '10♦' | '10♣' | '10♠' | 'J♥' | 'J♦' | 'J♣' | 'J♠' |
'Q♥' | 'Q♦' | 'Q♣' | 'Q♠' | 'K♥' | 'K♦' | 'K♣' | 'K♠';
type Symbols = '♥' | '♦' | '♣' | '♠';
type SymbolName = 'Hearts' | 'Diamonds' | 'Clubs' | 'Spades';
export class Blackjack extends Rooms.RoomGame {
room: Room;
roomID: RoomID;
autostart: NodeJS.Timer | null;
dqTimer: NodeJS.Timer | null;
timerTick: NodeJS.Timer | null;
cardHeight: number;
cardWidth: number;
minimumPlayers: number;
playerScrollWheel: number;
turnTimeoutMinutes: number;
timerTickSeconds: number;
button: string;
createdBy: string;
curUsername: string;
endedBy: string;
infoboxLimited: string;
lastMessage: string;
slideButton: string;
spectateButton: string;
startedBy: string;
state: string;
turnLog: string;
uhtmlChange: string;
spectators: {[k: string]: ID};
symbols: {[k in Symbols]: SymbolName};
dealer: BlackjackDealer;
deck: Deck[];
playerTable: {[k: string]: BlackjackPlayer};
gameNumber: number;
constructor(room: Room, user: User, autostartMinutes = 0) {
super(room);
this.gameNumber = room.nextGameNumber();
this.room = room;
this.turnTimeoutMinutes = 1;
this.timerTickSeconds = 5;
this.createdBy = user.name;
this.startedBy = '';
this.allowRenames = true;
this.playerCap = 16;
this.minimumPlayers = 2;
this.playerScrollWheel = 4;
this.cardWidth = 50;
this.cardHeight = 85;
this.spectators = Object.create(null);
this.playerTable = Object.create(null);
this.dealer = new BlackjackDealer();
this.symbols = {
'♥': 'Hearts',
'♦': 'Diamonds',
'♣': 'Clubs',
'♠': 'Spades',
};
this.deck = new BlackjackDeck().shuffle();
this.roomID = this.room.roomid;
this.title = `Blackjack (${room.title})`;
this.state = 'signups';
this.lastMessage = '';
this.turnLog = '';
this.uhtmlChange = '';
this.curUsername = '';
this.endedBy = '';
this.infoboxLimited = '';
this.button = '<button class="button" name="send" value="/joingame" title="Join Blackjack">Join</button> | <button class="button" name="send" value="/leavegame" title="Leave Blackjack">Leave</button>';
this.spectateButton = '<button class="button" name="send" value="/blackjack spectate" title="Spectate Blackjack">Spectate</button>';
this.slideButton = '<button class="button" name="send" value="/blackjack slide" title="Slide the game log down in the chat">(<i class="fa fa-arrow-down" aria-hidden="true"></i> slide)</button>';
this.autostart = null;
this.dqTimer = null;
this.timerTick = null;
this.makeGame(autostartMinutes);
}
/**
* Game Setup
* makeGame - configures required settings for creating a game
* makePlayer - adds blackjack-specific properties to player object
* sendInvite - called when a game is created, or when a player joins/leaves
*/
makeGame(autostartMinutes = 0) {
if (autostartMinutes > 0) {
this.autostart = setTimeout(() => this.start(), autostartMinutes * 60000);
}
this.sendInvite();
}
makePlayer(user: User) {
return new BlackjackPlayer(user, this);
}
sendInvite() {
const change = this.uhtmlChange;
const players = Object.keys(this.playerTable);
const playerList = [];
for (const player of players) playerList.push(Utils.escapeHTML(this.playerTable[player].name));
this.room.send(`|uhtml${change}|blackjack-${this.gameNumber}|<div class="infobox${this.infoboxLimited}">${this.createdBy} has created a game of Blackjack. ${this.button}<br /><strong>Players (${players.length}):</strong> ${!players.length ? '(None)' : playerList.join(', ')}</div>`);
this.uhtmlChange = 'change';
}
/**
* Joining/Leaving/Viewing
* joinGame - joins the game
* leaveGame - leaves the game
* spectate - spectates the game
* unspectate - stops spectating the game
*/
joinGame(user: User) {
if (!user.named) return this.errorMessage(user, `You must first choose a name to play Blackjack.`);
if (this.state === 'started') return this.errorMessage(user, `Blackjack has already started.`);
const joined = this.addPlayer(user);
if (!joined) {
this.errorMessage(user, `You are already in this game.`);
return false;
}
this.sendInvite();
if (Object.keys(this.playerTable).length === this.playerCap) {
this.start();
}
if (this.spectators[user.id]) delete this.spectators[user.id]; // prevent player from spectating
return true;
}
leaveGame(user: User) {
if (this.state === 'started') return this.errorMessage(user, `You cannot leave this game; it has already started.`);
if (!this.playerTable[user.id]) return this.errorMessage(user, "You are not in this game to leave.");
this.removePlayer(user);
this.sendInvite();
}
spectate(user: User) {
if (this.spectators[user.id]) return this.errorMessage(user, `You are already spectating this game.`);
if (this.playerTable[user.id]) {
return this.errorMessage(user, `You don't need to spectate the game; you're playing the game.`);
}
this.spectators[user.id] = user.id;
user.sendTo(this.roomid, `You are now spectating this game.`);
}
unspectate(user: User) {
if (!this.spectators[user.id]) return this.errorMessage(user, `You are already not spectating this game.`);
delete this.spectators[user.id];
user.sendTo(this.roomid, `You are no longer spectating this game.`);
}
/**
* Utility
* errorMessage - sends a user an error message
* add - adds/sends text to room
* display - displays gameplay to players and spectators
* clear - clears a user's gameplay screen
* clearAllTimers - clears all possible existing timers pertaining to blackjack
* slide - slides the game log down in the chat
* onConnect - handles replies to send when a user joins the room, if any
* onUpdateConnection - overrides default onUpdateConnection
* createTimer - creates a timer with a countdown for a player
* generateCard - generates the card for the UI
* getWinners - returns an array of the winners and their cards
*/
errorMessage(user: User, message: string) {
user.sendTo(this.room, Utils.html`|html|<div class="message-error">${message}</div>`);
}
send(message: string, clean = false) {
const change = this.uhtmlChange;
this.room.send(`|uhtml${change}|blackjack-${this.gameNumber}|<div class="infobox${this.infoboxLimited}">${(clean ? message : this.lastMessage + message)}</div>`);
this.lastMessage += message;
this.uhtmlChange = 'change';
}
display(text: string, clean = false, playerName?: string, noChange = false, end = false) {
const force = (end && this.endedBy);
let change = this.uhtmlChange;
if (noChange) change = '';
if (clean) this.lastMessage = '';
const message = `|uhtml${change}|blackjack-${this.gameNumber}|<div class="infobox${this.infoboxLimited}">`;
this.lastMessage += text;
if (end) {
text = `The game of blackjack has ${force ? `been forcibly ended by ${this.endedBy}` : 'ended'}. <details><summary>View turn log</summary>${this.turnLog}</details>${text}`;
this.lastMessage = '';
}
for (const player of Object.keys(this.playerTable)) {
if (playerName && this.playerTable[player].name === playerName) { // turn highlighting
this.playerTable[player].gameLog += `<span class="highlighted">${text}</span>`;
this.playerTable[player].sendRoom(`${message}${end ? text : this.playerTable[player].gameLog}</div>`);
} else {
this.playerTable[player].gameLog += text;
this.playerTable[player].sendRoom(`${message}${end ? text : this.playerTable[player].gameLog}</div>`);
}
}
for (const spectatorID of Object.keys(this.spectators)) {
const spectator = Users.get(this.spectators[spectatorID]);
if (spectator) spectator.sendTo(this.roomid, `${message}${this.lastMessage + text}</div>`);
}
}
clear() {
const player = this.playerTable[this.curUsername];
if (!player) throw new Error(`Player not in player table`); // this should never happen
player.sendRoom(`|uhtmlchange|user-blackjack-${this.gameNumber}|`);
}
clearAllTimers() {
if (this.dqTimer) {
clearTimeout(this.dqTimer);
this.dqTimer = null;
}
if (this.timerTick) {
clearInterval(this.timerTick);
this.timerTick = null;
}
if (this.autostart) {
clearTimeout(this.autostart);
this.autostart = null;
}
}
slide(user: User) {
user.sendTo(this.roomid, `|uhtml|blackjack-${this.gameNumber}|`);
this.display('', false, user.name);
}
onConnect(user: User) {
const message = `|uhtml|blackjack-${this.gameNumber}|<div class="infobox${this.infoboxLimited}">`;
if (this.state === 'signups') {
this.sendInvite();
} else if (this.state === 'started') {
const player = this.playerTable[user.id];
const spectator = this.spectators[user.id];
if (player && user.id === toID(this.curUsername)) { // their turn; send gamelog and game screen
player.sendRoom(`${message}${player.gameLog}`);
player.sendRoom(player.playScreen.replace('|uhtmlchange|', '|uhtml|'));
return;
} else if (player) { // a player, but not their turn; just send gamelog
player.sendRoom(`${message}${player.gameLog}`);
return;
} else if (spectator) { // spectator; send gamelog
user.sendTo(this.roomid, `${message}${this.lastMessage}`);
return;
}
}
}
onUpdateConnection() {
// This happens (runs onConnect) when a connection in the room
// changes, such as going on a new name. We don't want to do
// anything for such a situation, so we're overriding this.
}
createTimer(user: User) {
const player = this.playerTable[user.id];
this.dqTimer = setTimeout(() => {
let cards = '';
for (const card of player.cards) cards += `[${card}] `;
player.status = 'stand';
this.display(
Utils.html`<br /><strong>${player.name}</strong> stands with ${cards}` +
` (${player.points}) (Auto-stand: took too long to move)`,
false,
this.playerTable[this.curUsername].name
);
this.clear();
this.next();
}, this.turnTimeoutMinutes * 60 * 1000);
this.timerTick = setInterval(() => {
const display = player.playScreen.replace('|uhtml|', '|uhtmlchange|');
if (display !== '') {
const timeLeft = player.timerTicksLeft - 5;
const buffer = (String(timeLeft).length === 1 ? '0' : '');
const half = (timeLeft <= ((this.turnTimeoutMinutes * 60) / 2));
player.sendRoom(`${display} | <span${half ? ` class="message-error"` : ``}>[<i class="fa fa-hourglass-${half ? 2 : 1}" aria-hidden="true"></i> 0:${buffer}${timeLeft}]</span>`);
player.timerTicksLeft -= this.timerTickSeconds;
}
}, this.timerTickSeconds * 1000);
}
generateCard(card: string) {
const value = toID(card).toUpperCase();
const symbolName = this.symbols[card.substr(-1) as Symbols];
const red = ['D', 'H'].includes(symbolName.charAt(0));
let cardUI = value;
if (value === 'K') cardUI = 'King';
if (value === 'Q') cardUI = 'Queen';
if (value === 'A') cardUI = 'Ace';
if (value === 'J') cardUI = 'Joker';
return `<div title="${cardUI} of ${symbolName}" style="color: ${red ? '#992222' : '#000000'}; border-radius: 6px; background-color: #ffffff; position: relative; font-size: 14px; display: inline-block; width: ${this.cardWidth}px; height: ${this.cardHeight}px; border: 1px solid #000000; padding: 2px 4px;">${value}<br /> ${card.substr(-1)}<br /><span style="padding: 2px 4px; position: absolute; bottom: 0; right: 0; transform: rotate(-180deg);">${value}<br />${card.substr(-1)}</span></div> `;
}
getWinners(forceend = false) {
const winners = [];
if (forceend) this.giveCard('dealer');
if (this.dealer.points > 21) {
for (const player of Object.keys(this.playerTable)) {
if (this.playerTable[player].status === 'bust') continue;
winners.push(
Utils.html`<strong>${this.playerTable[player].name}</strong> ` +
`[${this.playerTable[player].cards.join(', ')}]`
);
}
} else if (this.dealer.points !== 21) {
for (const player of Object.keys(this.playerTable)) {
if (this.playerTable[player].status === 'bust' || this.playerTable[player].points <= this.dealer.points) continue;
winners.push(
Utils.html`<strong>${this.playerTable[player].name}</strong> ` +
`[${this.playerTable[player].cards.join(', ')}]`
);
}
} else if (this.dealer.points === 21) {
winners.push(`<strong>${this.dealer.name}</strong> [${this.dealer.cards.join(', ')}]`);
}
return winners;
}
/**
* Game State Changes
* start - starts the game
* end - ends the game
* destroy - destroys the game
*/
start(user?: User) {
const numberOfPlayers = Object.keys(this.playerTable).length;
if (numberOfPlayers < this.minimumPlayers) {
if (this.autostart) {
clearTimeout(this.autostart);
this.autostart = null;
}
this.send("<br />Not enough players to start; game canceled.");
this.destroy();
return;
}
if (user) this.startedBy = Utils.escapeHTML(user.name);
this.infoboxLimited = (numberOfPlayers >= this.playerScrollWheel ? ' infobox-limited' : '');
this.send(`[Blackjack has started. ${this.spectateButton}]`, true);
this.curUsername = Object.keys(this.playerTable)[0];
const header = `The game of blackjack has started${(this.startedBy !== '' ? ` (started by ${this.startedBy})` : ``)}. ${this.slideButton}<br />`;
this.state = 'started';
this.giveCard('dealer');
this.giveCard('dealer');
this.turnLog += `<strong>${this.dealer.name}</strong>: [${this.dealer.cards[0]}]`;
for (const player of Object.keys(this.playerTable)) {
this.giveCard(this.playerTable[player].user.id);
this.giveCard(this.playerTable[player].user.id);
this.turnLog += Utils.html`<br /><strong>${this.playerTable[player].name}</strong>: ` +
`[${this.playerTable[player].cards[0]}] [${this.playerTable[player].cards[1]}] (${this.playerTable[player].points})`;
}
this.display(`${header}${this.turnLog}`, true, '');
this.next();
}
end(user: User | true, cmd?: string) {
const force = (cmd && toID(cmd) === 'forceend');
if (user === true) {
this.state = 'ended';
this.destroy();
return true;
}
if (this.state === 'started' && cmd && !force) {
return this.errorMessage(
user,
`Because this game has started, you can only end this game by using /blackjack forceend.`
);
}
let winners = this.getWinners();
if (force) {
winners = this.getWinners(true);
this.endedBy = Utils.escapeHTML(user.name);
if (this.curUsername) {
this.playerTable[this.curUsername].send(`|uhtmlchange|user-blackjack-${this.gameNumber}|`);
}
if (winners.length < 1) {
this.display(`There are no winners this time.`, false, undefined, false, true);
} else {
this.display(`<strong>Winner${Chat.plural(winners.length)}</strong>: ${winners.join(', ')}`, false, undefined, false, true);
}
}
if (!force && this.state !== 'signups') {
if (winners.length < 1) {
this.display(`There are no winners this time.`, false, undefined, false, true);
} else {
this.display(`<strong>Winner${Chat.plural(winners.length)}</strong>: ${winners.join(', ')}`, false, undefined, false, true);
}
} else if (this.state === 'signups') {
this.send(
Utils.html`The game of blackjack has been ended by ${user.name}, ` +
`and there are no winners because the game never started.`,
true
);
}
this.state = 'ended';
this.destroy();
return true;
}
destroy() {
if (Object.keys(this.playerTable).length) {
for (const player of Object.keys(this.playerTable)) {
this.playerTable[player].destroy();
}
}
this.clearAllTimers();
this.room.game = null;
}
/**
* Gameplay
* hit - player decides to get a new card
* stand - player decides to keep current cards
* giveCard - gives a player a card from the deck
* getCardPoints - returns the point value of a user's cards
* next - game goes on to the next turn
*/
hit(user: User) {
if (this.state !== 'started') return this.errorMessage(user, `Blackjack hasn't started yet.`);
if (!this.playerTable[user.id]) return this.errorMessage(user, `You aren't a player in this game.`);
if (this.curUsername !== user.id) return this.errorMessage(user, `It's not your turn.`);
this.playerTable[user.id].selfUhtml = 'change';
this.playerTable[user.id].resetTimerTicks();
this.giveCard(user.id);
}
stand(user: User) {
const player = this.playerTable[user.id];
if (this.state !== 'started') return this.errorMessage(user, `Blackjack hasn't started yet.`);
if (!player) return this.errorMessage(user, `You aren't a player in this game.`);
if (this.curUsername !== user.id) return this.errorMessage(user, `It's not your turn.`);
player.status = 'stand';
let cards = '';
for (const card of player.cards) cards += `[${card}] `;
const turnLine = Utils.html`<br /><strong>${player.name}</strong> stands with ${cards} ` +
`(${player.points}${player.points === 21 ? ' - blackjack!' : ''})`;
this.turnLog += turnLine;
this.display(turnLine, false, player.name);
this.clear();
this.next();
}
giveCard(userid: string) {
if (this.deck.length < 1) this.deck = new BlackjackDeck().shuffle();
const player = (userid === 'dealer' ? this.dealer : this.playerTable[userid]);
if (!player) return; // this should never happen
player.cards.push(this.deck[0]);
this.deck.shift();
if (this.deck.length === 0) {
this.display(`<br />${this.dealer.name} has ran out of cards in the deck; shuffling a new deck...`);
this.deck = new BlackjackDeck().shuffle();
}
player.points = this.getCardPoints(userid);
if (player.cards.length < 3) return;
let turnLine = Utils.html`<br /><strong>${player.name}</strong> hit and received ` +
`[${player.cards[player.cards.length - 1]}] (${player.points})`;
this.turnLog += turnLine;
if (player.cards.length > 2) this.display(turnLine, false, player.name);
if (player === this.dealer) {
if (player.points > 21) {
let cards = '';
for (const card of player.cards) {
cards += `[${card}] `;
}
turnLine = Utils.html`<br /><strong>${this.dealer.name}</strong> has busted with ${cards} (${player.points})`;
this.turnLog += turnLine;
this.display(turnLine);
this.end(true);
return;
} else if (player.points === 21) {
turnLine = Utils.html`<br /><strong>${this.dealer.name}</strong> has blackjack!`;
this.turnLog += turnLine;
this.display(turnLine);
this.end(true);
return;
}
} else if (player.points > 21) {
(player as BlackjackPlayer).status = 'bust';
let cards = '';
for (const card of player.cards) {
cards += `[${card}] `;
}
turnLine = Utils.html`<br /><strong>${player.name}</strong> has busted with ${cards} (${player.points})`;
this.turnLog += turnLine;
this.display(turnLine, false, player.name);
this.clear();
} else if (player.points === 21) {
(player as BlackjackPlayer).status = 'stand';
turnLine = Utils.html`<br /><strong>${player.name}</strong> has blackjack!`;
this.turnLog += turnLine;
this.display(turnLine, false, player.name);
this.clear();
}
if (player !== this.dealer) this.playerTable[userid].cards = player.cards;
this.next();
}
getCardPoints(playerid: string) {
const player = (playerid === 'dealer' ? this.dealer : this.playerTable[playerid]);
let points = 0;
let aceCount = 0;
for (const card of player.cards.map(x => toID(x).toUpperCase())) {
if (!isNaN(Number(card))) {
points += Number(card);
} else if (['K', 'Q', 'J'].includes(card)) {
points += 10;
} else if (card === 'A') {
points += 11;
aceCount++;
}
}
// At first, we value aces as 11, however, we will change their value
// to be 1 if having them as 11 would cause an unnecessary bust. We will
// do this by subtracting 10 for each ace that would otherwise cause a bust.
while (points > 21 && aceCount > 0) {
points -= 10;
aceCount--;
}
return points;
}
next() {
this.clearAllTimers();
if (
Object.keys(this.playerTable)[Object.keys(this.playerTable).length - 1] === this.curUsername &&
this.playerTable[this.curUsername].status !== 'playing'
) {
if (this.dealer.points < 17) {
this.giveCard('dealer');
} else if (this.dealer.points >= 17) {
let cards = '';
for (const card of this.dealer.cards) {
cards += `[${card}] `;
}
const turnLine = `<br /><strong>${this.dealer.name}</strong> stands with ${cards} (${this.dealer.points})`;
this.turnLog += turnLine;
this.display(turnLine);
this.end(true);
}
return;
}
if (this.playerTable[this.curUsername].status !== 'playing') {
let num = 0;
while (this.playerTable[Object.keys(this.playerTable)[num]].status !== 'playing') {
num++;
}
this.curUsername = Object.keys(this.playerTable)[num];
}
let output = `|uhtml${this.playerTable[this.curUsername].selfUhtml}|user-blackjack-${this.gameNumber}|<div class="infobox" style="line-height: 20px;">`;
output += `It's your turn to move, ${this.playerTable[this.curUsername].name}<br />`;
for (const card of this.playerTable[this.curUsername].cards) {
output += this.generateCard(card);
}
output += `<br />Score: ${this.playerTable[this.curUsername].points}${(this.playerTable[this.curUsername].points === 21 ? ` (you have blackjack!)` : ``)}`;
output += `<br /><button class="button" name="send" value="/blackjack hit" title="Hit (get another card)">Hit</button> | <button class="button" name="send" value="/blackjack stand" title="Stand (just keep these cards)">Stand</button>`;
this.playerTable[this.curUsername].sendRoom(`|notify|Blackjack (${this.room.title})|It's your turn to play!`);
this.playerTable[this.curUsername].sendRoom(output);
this.playerTable[this.curUsername].playScreen = output;
this.createTimer(this.playerTable[this.curUsername].user);
}
}
class BlackjackPlayer extends Rooms.RoomGamePlayer {
user: User;
game: Blackjack;
points: number;
slide: number;
timerTicksLeft: number;
cards: string[];
gameLog: string;
playScreen: string;
selfUhtml: string;
status: string;
constructor(user: User, game: Blackjack) {
super(user, game);
this.user = user;
this.game = game;
this.cards = [];
this.points = 0;
this.slide = 0;
this.status = 'playing';
this.selfUhtml = '';
this.gameLog = '';
this.playScreen = '';
this.timerTicksLeft = this.game.turnTimeoutMinutes * 60; // to get into seconds-format
}
resetTimerTicks() {
this.timerTicksLeft = this.game.turnTimeoutMinutes * 60;
}
}
class BlackjackDealer {
cards: string[];
points: number;
name: string;
constructor() {
this.cards = [];
this.points = 0;
this.name = 'The Dealer';
}
}
class BlackjackDeck {
deck: Deck[];
constructor() {
this.deck = [
'A♥', 'A♦', 'A♣', 'A♠', '2♥', '2♦', '2♣', '2♠', '3♥', '3♦', '3♣',
'3♠', '4♥', '4♦', '4♣', '4♠', '5♥', '5♦', '5♣', '5♠', '6♥', '6♦', '6♣', '6♠',
'7♥', '7♦', '7♣', '7♠', '8♥', '8♦', '8♣', '8♠', '9♥', '9♦', '9♣', '9♠', '10♥',
'10♦', '10♣', '10♠', 'J♥', 'J♦', 'J♣', 'J♠', 'Q♥', 'Q♦', 'Q♣', 'Q♠', 'K♥', 'K♦',
'K♣', 'K♠',
];
}
shuffle() {
return Utils.shuffle(this.deck);
}
}
export const commands: ChatCommands = {
blj: 'blackjack',
blackjack: {
new: 'create',
create(target, room, user) {
room = this.requireRoom();
this.checkCan('minigame', null, room);
if (room.game) return this.errorReply("There is already a game running in this room.");
if (room.settings.blackjackDisabled) return this.errorReply("Blackjack is currently disabled in this room.");
const autostartMinutes = target ? parseFloat(target) : 0;
if (isNaN(autostartMinutes) || autostartMinutes <= 0 || (autostartMinutes * 60 * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply("Usage: /blackjack create [autostart] - where autostart is an integer");
}
this.privateModAction(`A game of blackjack was created by ${user.name}.`);
this.modlog(`BLACKJACK CREATE`);
room.game = new Blackjack(room, user, autostartMinutes);
},
start(target, room, user) {
room = this.requireRoom();
this.checkCan('minigame', null, room);
const game = this.requireGame(Blackjack);
if (game.state !== 'signups') return this.errorReply("This game of blackjack has already started.");
this.privateModAction(`The game of blackjack was started by ${user.name}.`);
this.modlog(`BLACKJACK START`);
game.start(user);
},
forceend: 'end',
end(target, room, user, connection, cmd) {
room = this.requireRoom();
this.checkCan('minigame', null, room);
const game = this.requireGame(Blackjack);
const force = cmd === 'forceend' ? 'forcibly ' : '';
const end = game.end(user, cmd);
if (end) {
this.privateModAction(`The game of blackjack was ${force}ended by ${user.name}.`);
this.modlog(`BLACKJACK END`);
}
},
hit(target, room, user) {
room = this.requireRoom();
const game = this.requireGame(Blackjack);
game.hit(user);
},
stand(target, room, user) {
room = this.requireRoom();
const game = this.requireGame(Blackjack);
game.stand(user);
},
slide(target, room, user) { // undocumented (used in UI)
room = this.requireRoom();
const game = this.requireGame(Blackjack);
game.slide(user);
},
j: 'join',
join(target, room, user) {
return this.parse('/joingame');
},
l: 'leave',
leave(target, room, user) {
return this.parse('/leavegame');
},
unspectate: 'spectate',
spectate(target, room, user, connection, cmd) {
room = this.requireRoom();
const game = this.requireGame(Blackjack);
if (cmd === 'spectate') {
game.spectate(user);
} else if (cmd === 'unspectate') {
game.unspectate(user);
}
},
disable(target, room, user) {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (room.settings.blackjackDisabled) {
return this.errorReply("Blackjack is already disabled in this room.");
}
room.settings.blackjackDisabled = true;
room.saveSettings();
this.sendReply(`Blackjack has been disabled for this room.`);
},
enable(target, room, user) {
room = this.requireRoom();
this.checkCan('gamemanagement', null, room);
if (!room.settings.blackjackDisabled) {
return this.errorReply("Blackjack is already enabled in this room.");
}
delete room.settings.blackjackDisabled;
room.saveSettings();
this.sendReply(`Blackjack has been enabled for this room.`);
},
'': 'help',
help(target, room, user) {
return this.parse('/help blackjack');
},
},
blackjackhelp: [
"/blackjack create - Creates a game of blackjack. Requires: % @ # &",
"/blackjack create [autostart] - Automatically creates a game of blackjack in [autostart] minutes. Requires: % @ # &",
"/blackjack start - Starts a game of blackjack. Requires: % @ # &",
"/blackjack end - Ends a game of blackjack. Requires: % @ # &",
"/blackjack join - Joins a game of blackjack.",
"/blackjack leave - Leaves a game of blackjack.",
"/blackjack spectate - Spectates a game of blackjack.",
"/blackjack unspectate - Stops spectating a game of blackjack.",
"/blackjack disable - Prevents games of blackjack from being made in the room. Requires: # &",
"/blackjack enable - Allows games of blackjack to be made in the room. Requires: # &",
],
};
export const roomSettings: SettingsHandler = room => ({
label: "Blackjack",
permission: 'editroom',
options: [
[`disabled`, room.settings.blackjackDisabled || 'blackjack disable'],
[`enabled`, !room.settings.blackjackDisabled || 'blackjack enable'],
],
});