diff --git a/files/assets/js/blackjack_screen.js b/files/assets/js/blackjack_screen.js
new file mode 100644
index 000000000..5040d7fd4
--- /dev/null
+++ b/files/assets/js/blackjack_screen.js
@@ -0,0 +1,224 @@
+function makeBlackjackRequest(action) {
+ const xhr = new XMLHttpRequest();
+ xhr.open("post", `/casino/twentyone/${action}`);
+ xhr.setRequestHeader('xhr', 'xhr');
+ xhr.onload = handleBlackjackResponse.bind(null, xhr);
+ xhr.blackjackAction = action;
+ return xhr;
+}
+
+function handleBlackjackResponse(xhr) {
+ let status;
+ try {
+ const response = JSON.parse(xhr.response);
+ const succeeded = xhr.status >= 200 &&
+ xhr.status < 300 &&
+ response &&
+ !response.error;
+
+ clearResult();
+ status = xhr.status;
+
+ if (status == 429) {
+ throw new Error(response["details"]);
+ }
+
+ if (succeeded) {
+ updateBlackjackTable(response.state);
+ updateFeed(response.feed);
+ updatePlayerCurrencies(response.gambler);
+ } else {
+ console.error("Error: ", response.error);
+ throw new Error("Error")
+ }
+ } catch (error) {
+ const results = {
+ deal: "Unable to deal a new hand. Is one in progress?",
+ hit: "Unable to hit.",
+ stay: "Unable to stay.",
+ "double-down": "Unable to double down.",
+ "buy-insurance": "Unable to buy insurance."
+ };
+ result = results[xhr.blackjackAction];
+
+ if (status == 429) {
+ result = error.message;
+ }
+
+ updateResult(result, "danger");
+ }
+}
+
+function updateBlackjackActions(state) {
+ const actions = Array.from(document.querySelectorAll('.twentyone-btn'));
+
+ // Hide all actions.
+ actions.forEach(action => action.style.display = 'none');
+
+ if (state) {
+ // Show the correct ones.
+ state.actions.forEach(action => document.getElementById(`twentyone-${action}`).style.display = 'inline-block');
+ } else {
+ const dealButton = document.getElementById(`twentyone-DEAL`);
+
+ setTimeout(() => {
+ const dealButton = document.getElementById(`twentyone-DEAL`);
+ })
+
+ if (dealButton) {
+ dealButton.style.display = 'inline-block'
+ }
+ }
+}
+
+function updateBlackjackTable(state) {
+ const table = document.getElementById('blackjack-table');
+ const charactersToRanks = {
+ X: "10"
+ };
+ const charactersToSuits = {
+ S: "♠️",
+ H: "♥️",
+ C: "♣️",
+ D: "♦️",
+ };
+ const makeCardset = (from, who, value) => `
+
+
+ ${value === -1 ? `${who} went bust` : `${who} has ${value}`}
+
+ ${from
+ .filter(card => card !== "?")
+ .map(([rankCharacter, suitCharacter]) => {
+ const rank = charactersToRanks[rankCharacter] || rankCharacter;
+ const suit = charactersToSuits[suitCharacter] || suitCharacter;
+ return buildPlayingCard(rank, suit);
+ })
+ .join('')}
+
+ `;
+ const dealerCards = makeCardset(state.dealer, 'Dealer', state.dealer_value);
+ const playerCards = makeCardset(state.player, 'Player', state.player_value);
+
+ updateBlackjackActions(state);
+
+ table.innerHTML = `
+
+ ${dealerCards}
+
+ ${playerCards}
+ `;
+
+ const currency = state.wager.currency === 'coins' ? 'coins' : 'marseybux';
+
+ switch (state.status) {
+ case 'BLACKJACK':
+ updateResult(`Blackjack: Received ${state.payout} ${currency}`, "warning");
+ break;
+ case 'WON':
+ updateResult(`Won: Received ${state.payout} ${currency}`, "success");
+ break;
+ case 'PUSHED':
+ updateResult(`Pushed: Received ${state.wager.amount} ${currency}`, "success");
+ break;
+ case 'LOST':
+ updateResult(`Lost ${state.wager.amount} ${currency}`, "danger");
+ break;
+ default:
+ break;
+ }
+
+ updateCardsetBackgrounds(state);
+
+ if (state.status === 'PLAYING') {
+ updateResult(`${state.wager.amount} ${currency} are at stake`, "success");
+ } else {
+ enableWager();
+ }
+}
+
+function updateCardsetBackgrounds(state) {
+ const cardsets = Array.from(document.querySelectorAll('.blackjack-cardset'));
+
+ for (const cardset of cardsets) {
+ ['PLAYING', 'LOST', 'PUSHED', 'WON', 'BLACKJACK'].forEach(status => cardset.classList.remove(`blackjack-cardset__${status}`));
+ cardset.classList.add(`blackjack-cardset__${state.status}`)
+ }
+}
+
+function deal() {
+ const request = makeBlackjackRequest('deal');
+ const { amount, currency } = getWager();
+ const form = new FormData();
+
+ form.append("formkey", formkey());
+ form.append("wager", amount);
+ form.append("currency", currency);
+
+ request.send(form);
+
+ clearResult();
+ disableWager();
+ drawFromDeck();
+}
+
+function hit() {
+ const request = makeBlackjackRequest('hit');
+ const form = new FormData();
+ form.append("formkey", formkey());
+ request.send(form);
+
+ drawFromDeck();
+}
+
+function stay() {
+ const request = makeBlackjackRequest('stay');
+ const form = new FormData();
+ form.append("formkey", formkey());
+ request.send(form);
+}
+
+function doubleDown() {
+ const request = makeBlackjackRequest('double-down');
+ const form = new FormData();
+ form.append("formkey", formkey());
+ request.send(form);
+
+ drawFromDeck();
+}
+
+function buyInsurance() {
+ const request = makeBlackjackRequest('buy-insurance');
+ const form = new FormData();
+ form.append("formkey", formkey());
+ request.send(form);
+}
+
+function buildBlackjackDeck() {
+ document.getElementById('blackjack-table-deck').innerHTML = `
+
+ ${buildPlayingCardDeck()}
+
+ `;
+}
+
+function initializeBlackjack() {
+ buildBlackjackDeck();
+
+ try {
+ const passed = document.getElementById('blackjack-table').dataset.state;
+ const state = JSON.parse(passed);
+ updateBlackjackTable(state);
+ } catch (error) {
+ updateBlackjackActions();
+ }
+}
+
+if (
+ document.readyState === "complete" ||
+ (document.readyState !== "loading" && !document.documentElement.doScroll)
+) {
+ initializeBlackjack();
+} else {
+ document.addEventListener("load", initializeBlackjack);
+}
diff --git a/files/assets/js/game_screen.js b/files/assets/js/game_screen.js
new file mode 100644
index 000000000..72d762842
--- /dev/null
+++ b/files/assets/js/game_screen.js
@@ -0,0 +1,201 @@
+/**
+ * This script block contains generic helper function usable across casino games:
+ * - Wagers
+ * - Feed
+ * - Leaderboard
+ */
+
+if (
+ document.readyState === "complete" ||
+ (document.readyState !== "loading" && !document.documentElement.doScroll)
+) {
+ initializeGame();
+} else {
+ document.addEventListener("load", initializeGame);
+}
+
+function initializeGame() {
+ updateFeed();
+ updateLeaderboard();
+}
+
+function updatePlayerCurrencies(updated) {
+ if (updated.coins) {
+ document.getElementById("user-coins-amount").innerText = updated.coins;
+ }
+
+ if (updated.marseybux) {
+ document.getElementById("user-bux-amount").innerText = updated.marseybux;
+ }
+}
+
+function getWager() {
+ const amount = document.getElementById("wagerAmount").value;
+ const currency = document.querySelector(
+ 'input[name="wagerCurrency"]:checked'
+ ).value;
+ const genericCurrency = currency == 'marseybux' ? 'marseybux' : 'coins';
+
+ return { amount, currency: genericCurrency, localCurrency: currency };
+}
+
+function disableWager() {
+ document.getElementById("wagerAmount").disabled = true;
+ document.getElementById("wagerCoins").disabled = true;
+ document.getElementById("wagerMarseybux").disabled = true;
+}
+
+function enableWager() {
+ document.getElementById("wagerAmount").disabled = false;
+ document.getElementById("wagerCoins").disabled = false;
+ document.getElementById("wagerMarseybux").disabled = false;
+}
+
+function updateResult(text, className) {
+ clearResult();
+ const result = document.getElementById("casinoGameResult");
+ result.style.visibility = "visible";
+ result.innerText = text;
+ result.classList.add(`alert-${className}`);
+}
+
+function clearResult() {
+ const result = document.getElementById("casinoGameResult");
+ result.style.visibility = "hidden";
+ result.innerText = "N/A";
+ result.classList.remove("alert-success", "alert-danger", "alert-warning");
+}
+
+function updateFeed(newFeed) {
+ let feed;
+
+ if (newFeed) {
+ feed = newFeed;
+ } else {
+ const gameFeed = document.getElementById("casinoGameFeed");
+ feed = gameFeed.dataset.feed;
+ feed = JSON.parse(feed);
+ gameFeed.dataset.feed = "";
+ }
+
+ const feedHtml = feed
+ .map(
+ (entry) =>
+ `
+
+
+
@${entry.user} ${entry.won_or_lost} ${entry.amount
+ } ${entry.currency}
+
+
+ `
+ )
+ .join("");
+
+ document.getElementById("casinoGameFeedList").innerHTML = feedHtml;
+}
+
+function reloadFeed() {
+ const game = document.getElementById('casino-game-wrapper').dataset.game;
+ const xhr = new XMLHttpRequest();
+ xhr.open("get", `/casino/${game}/feed`);
+ xhr.setRequestHeader('xhr', 'xhr');
+ xhr.onload = handleFeedResponse.bind(null, xhr);
+ xhr.send();
+}
+
+function handleFeedResponse(xhr) {
+ let response;
+
+ try {
+ response = JSON.parse(xhr.response);
+ } catch (error) {
+ console.error(error);
+ }
+
+ const succeeded =
+ xhr.status >= 200 && xhr.status < 300 && response && !response.error;
+
+ if (succeeded) {
+ document.getElementById("casinoGameFeed").dataset.feed = JSON.stringify(response.feed);
+ updateFeed();
+ } else {
+ console.error("error");
+ }
+}
+
+function updateLeaderboard() {
+ const leaderboardContainer = document.getElementById("gameLeaderboard");
+ const leaderboardData = JSON.parse(leaderboardContainer.dataset.leaderboard);
+ const [biggestWinnerAllTime, biggestWinner24h, biggestLoser24h, biggestLoserAllTime] = [
+ 'biggestWinnerAllTime', 'biggestWinner24h', 'biggestLoser24h', 'biggestLoserAllTime'
+ ].map(id => document.getElementById(id));
+ const formatLocalCurrencyName = currency => ({ coins: 'coins', marseybux: 'marseybux' })[currency];
+
+ biggestWinnerAllTime.innerHTML = `
+ ${leaderboardData.all_time.biggest_win.user}
${leaderboardData.all_time.biggest_win.amount} ${formatLocalCurrencyName(leaderboardData.all_time.biggest_win.currency)}
+ `;
+
+ biggestWinner24h.innerHTML = `
+ ${leaderboardData.last_24h.biggest_win.user}
${leaderboardData.last_24h.biggest_win.amount} ${formatLocalCurrencyName(leaderboardData.last_24h.biggest_win.currency)}
+ `;
+
+ biggestLoser24h.innerHTML = `
+ ${leaderboardData.last_24h.biggest_loss.user}
${leaderboardData.last_24h.biggest_loss.amount} ${formatLocalCurrencyName(leaderboardData.last_24h.biggest_loss.currency)}
+ `;
+
+ biggestLoserAllTime.innerHTML = `
+ ${leaderboardData.all_time.biggest_loss.user}
${leaderboardData.all_time.biggest_loss.amount} ${formatLocalCurrencyName(leaderboardData.all_time.biggest_loss.currency)}
+ `;
+}
+
+function getRandomInt(min, max) {
+ min = Math.ceil(min);
+ max = Math.floor(max);
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+}
+
+function getRandomCardAngle() {
+ const skew = 10
+ return getRandomInt(-skew, skew);
+}
+
+function buildPlayingCard(rank, suit) {
+ return `
+
+
${rank}${suit}
+
${rank}${suit}
+
${rank}${suit}
+
+ `;
+}
+
+function buildPlayingCardDeck(size = 14) {
+ const cards = Array.from({ length: size }, (_, index) => `
+
+ `).join('\n');
+
+ return `
+
+ ${cards}
+
+ `;
+}
+
+function drawFromDeck() {
+ try {
+ const [topCard] = Array.from(document.querySelectorAll("#playingCardDeck > *")).reverse();
+
+ topCard.classList.add('drawing-a-card');
+
+ setTimeout(() => {
+ topCard.classList.remove('drawing-a-card');
+ }, 600);
+ } catch { }
+}
diff --git a/files/assets/js/roulette_screen.js b/files/assets/js/roulette_screen.js
new file mode 100644
index 000000000..ca1659d39
--- /dev/null
+++ b/files/assets/js/roulette_screen.js
@@ -0,0 +1,387 @@
+// Kiss my ass if you're judgin'
+const CELL_TO_NUMBER_LOOKUP = {
+ 1: 3,
+ 2: 6,
+ 3: 9,
+ 4: 12,
+ 5: 15,
+ 6: 18,
+ 7: 21,
+ 8: 24,
+ 9: 27,
+ 10: 30,
+ 11: 33,
+ 12: 36,
+ 13: 2,
+ 14: 5,
+ 15: 8,
+ 16: 11,
+ 17: 14,
+ 18: 17,
+ 19: 20,
+ 20: 23,
+ 21: 26,
+ 22: 29,
+ 23: 32,
+ 24: 35,
+ 25: 1,
+ 26: 4,
+ 27: 7,
+ 28: 10,
+ 29: 13,
+ 30: 16,
+ 31: 19,
+ 32: 22,
+ 33: 25,
+ 34: 28,
+ 35: 31,
+ 36: 34
+};
+
+function initializeGame() {
+ buildRouletteTable();
+ updateResult("Rolls occur every five minutes", "success");
+ requestRouletteBets();
+}
+
+function buildRouletteTable() {
+ const table = document.getElementById('roulette-table');
+ const reds = [1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36];
+
+ let html = "";
+
+ // Lines
+ html += `
+
+
+
Line 1
+
Line 2
+
Line 3
+
Line 4
+
Line 5
+
Line 6
+
+
+ `;
+
+ // First Column
+ html += "";
+ html += `
00
`
+ for (let i = 1; i < 13; i++) {
+ const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
+ const isRed = reds.includes(correctNumber);
+
+ html += `
+ ${correctNumber}
+
+ `;
+ }
+ html += `
Col 3
`;
+ html += "
";
+
+ // Second Column
+ html += "";
+ html += `
`
+ for (let i = 13; i < 25; i++) {
+ const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
+ const isRed = reds.includes(correctNumber);
+
+ html += `
+ ${correctNumber}
+
+ `;
+ }
+ html += `
Col 2
`;
+ html += "
";
+
+ // Third Column
+ html += "";
+ html += `
0
`
+ for (let i = 25; i < 37; i++) {
+ const correctNumber = CELL_TO_NUMBER_LOOKUP[i];
+ const isRed = reds.includes(correctNumber);
+
+ html += `
+ ${correctNumber}
+
+ `;
+ }
+ html += `
Col 1
`;
+ html += "
";
+
+ // Line Bets and 1:1 Bets
+ html += `
+
+
+
1st12
+
2nd12
+
3rd12
+
+
+
+
+
1:18
+
EVEN
+
RED
+
BLACK
+
ODD
+
19:36
+
+
+ `;
+
+ table.innerHTML = html;
+}
+
+function formatFlatBets(bets) {
+ let flatBets = [];
+
+ for (const betCollection of Object.values(bets)) {
+ flatBets = flatBets.concat(betCollection)
+ }
+
+ return flatBets;
+}
+
+function formatNormalizedBets(bets) {
+ const normalizedBets = {
+ gamblers: [],
+ gamblersByName: {}
+ };
+ const flatBets = formatFlatBets(bets);
+
+ for (const bet of flatBets) {
+ if (!normalizedBets.gamblers.includes(bet.gambler_username)) {
+ normalizedBets.gamblers.push(bet.gambler_username);
+ }
+
+ if (!normalizedBets.gamblersByName[bet.gambler_username]) {
+ normalizedBets.gamblersByName[bet.gambler_username] = {
+ name: bet.gambler_username,
+ avatar: bet.gambler_profile_url,
+ profile: `/@${bet.gambler_username}`,
+ wagerTotal: {
+ coins: 0,
+ marseybux: 0
+ },
+ wagers: []
+ }
+ }
+
+ const entry = normalizedBets.gamblersByName[bet.gambler_username];
+
+ entry.wagerTotal[bet.wager.currency] += bet.wager.amount;
+
+ const existingWager = entry.wagers.find(wager => wager.bet === bet.bet && wager.which === bet.which);
+
+ if (existingWager) {
+ existingWager.amounts[bet.wager.currency] += bet.wager.amount;
+ } else {
+ const newEntry = {
+ bet: bet.bet,
+ which: bet.which,
+ amounts: {
+ coins: 0,
+ marseybux: 0
+ },
+ };
+ newEntry.amounts[bet.wager.currency] += bet.wager.amount;
+
+ entry.wagers.push(newEntry);
+ }
+ }
+
+ return normalizedBets;
+}
+
+function buildPokerChip(avatar) {
+ return `
+
+
+
+
+ `;
+}
+
+function buildRouletteBets(bets) {
+ const betArea = document.getElementById("roulette-bets");
+ const flatBets = formatFlatBets(bets);
+ const normalizedBets = formatNormalizedBets(bets);
+ const coinImgHtml = `
+
+ `;
+ const marseybuxImgHtml = `
+
+ `;
+ const { participants, coin, marseybux } = flatBets.reduce((prev, next) => {
+ if (!prev.participants.includes(next.gambler_username)) {
+ prev.participants.push(next.gambler_username);
+ }
+
+ if (next.wager.currency == 'coins') {
+ prev.coin += next.wager.amount;
+ } else {
+ prev.marseybux += next.wager.amount;
+ }
+
+ return prev;
+ }, { participants: [], coin: 0, marseybux: 0 });
+ const coinText = `${coin} ${coinImgHtml}`;
+ const marseybuxText = `${marseybux} ${marseybuxImgHtml}`;
+ const playerText = participants.length > 1 ? `${participants.length} players are` : `1 player is`;
+ const totalText = coin && marseybux ? `${coinText} and ${marseybuxText}` : coin ? coinText : marseybuxText;
+ const fullTotalText = participants.length === 0 ? "No one has placed a bet" : `${playerText} betting a total of ${totalText}`;
+
+ let betHtml = `
+ ${fullTotalText}
+
+ `;
+
+ for (player of normalizedBets.gamblers) {
+ const { name, avatar, wagerTotal, wagers } = normalizedBets.gamblersByName[player];
+
+ betHtml += ``;
+ // Heading
+ betHtml += `
`;
+ betHtml += buildPokerChip(avatar);
+ const coinText = wagerTotal.coins > 0 ? `${wagerTotal.coins} ${coinImgHtml}` : "";
+ const procoinText = wagerTotal.marseybux > 0 ? `${wagerTotal.marseybux} ${marseybuxImgHtml}` : "";
+ const bettingText = coinText && procoinText ? `${coinText} and ${procoinText}` : coinText || procoinText;
+ betHtml += `
${name} is betting ${bettingText}:
`;
+ betHtml += `
`;
+
+ // Individual bets
+ betHtml += `
`;
+ for (const individualBet of wagers) {
+ const coinText = individualBet.amounts.coins > 0 ? `${individualBet.amounts.coins} ${coinImgHtml}` : "";
+ const procoinText = individualBet.amounts.marseybux > 0 ? `${individualBet.amounts.marseybux} ${marseybuxImgHtml}` : "";
+ const straightUpWhich = (individualBet.which == 37) ? "00" : individualBet.which;
+ const details = {
+ STRAIGHT_UP_BET: `that the number will be ${straightUpWhich}`,
+ LINE_BET: `that the number will be within line ${individualBet.which}`,
+ COLUMN_BET: `that the number will be within column ${individualBet.which}`,
+ DOZEN_BET: `that the number will be within dozen ${individualBet.which}`,
+ EVEN_ODD_BET: `that the number will be ${individualBet.which.toLowerCase()}`,
+ RED_BLACK_BET: `that the color of the number will be ${individualBet.which.toLowerCase()}`,
+ HIGH_LOW_BET: `that the number will be ${individualBet.which === "HIGH" ? "higher than 18" : "lower than 19"}`
+ }
+ const betText = coinText && procoinText ? `${coinText} and ${procoinText}` : coinText || procoinText;
+
+ betHtml += `- ${betText} ${details[individualBet.bet]}
`;
+ }
+ betHtml += `
`;
+ betHtml += `
`;
+ }
+
+ betArea.innerHTML = betHtml;
+}
+
+function placeChip(bet, which) {
+ const { amount, currency: safeCurrency, localCurrency: currency } = getWager();
+ const whichNice = which == 37 ? "00" : which;
+ const texts = {
+ STRAIGHT_UP_BET: `Bet ${amount} ${currency} on ${whichNice}?\nYou could win ${amount * 35} ${currency}.`,
+ LINE_BET: `Bet ${amount} ${currency} on line ${which}?\nYou could win ${amount * 5} ${currency}.`,
+ COLUMN_BET: `Bet ${amount} ${currency} column ${which}?\nYou could win ${amount * 2} ${currency}.`,
+ DOZEN_BET: `Bet ${amount} ${currency} dozen ${which}?\nYou could win ${amount * 2} ${currency}.`,
+ EVEN_ODD_BET: `Bet ${amount} ${currency} that the number will be ${which.toLowerCase()}?\nYou could win ${amount} ${currency}.`,
+ RED_BLACK_BET: `Bet ${amount} ${currency} that the number will be ${which.toLowerCase()}?\nYou could win ${amount} ${currency}.`,
+ HIGH_LOW_BET: `Bet ${amount} ${currency} that the number will be ${which === "HIGH" ? "higher than 18" : "lower than 19"}?\nYou could win ${amount} ${currency}.`,
+ }
+ const text = texts[bet] || "";
+ const confirmed = window.confirm(text);
+
+ if (confirmed) {
+ const xhr = new XMLHttpRequest();
+ xhr.open("post", "/casino/roulette/place-bet");
+ xhr.setRequestHeader('xhr', 'xhr');
+ xhr.onload = handleRouletteResponse.bind(null, xhr);
+
+ const form = new FormData();
+ form.append("formkey", formkey());
+ form.append("bet", bet);
+ form.append("which", which);
+ form.append("wager", amount);
+ form.append("currency", safeCurrency);
+
+ xhr.send(form);
+ }
+}
+
+function addChipsToTable(bets) {
+ const flatBets = formatFlatBets(bets);
+
+ for (const bet of flatBets) {
+ const tableElement = document.getElementById(`${bet.bet}#${bet.which}`);
+ tableElement.style.position = 'relative';
+ const count = tableElement.dataset.count ? parseInt(tableElement.dataset.count) + 1 : 1;
+ tableElement.dataset.count = count;
+
+ const chip = buildPokerChip(bet.gambler_profile_url)
+ tableElement.innerHTML = `${tableElement.innerHTML}${chip}
`;
+ }
+}
+
+function requestRouletteBets() {
+ const xhr = new XMLHttpRequest();
+ xhr.open("get", "/casino/roulette/bets");
+ xhr.setRequestHeader('xhr', 'xhr');
+ xhr.onload = handleRouletteResponse.bind(null, xhr);
+ xhr.send();
+}
+
+function handleRouletteResponse(xhr) {
+ let response;
+
+ try {
+ response = JSON.parse(xhr.response);
+ } catch (error) {
+ console.error(error);
+ }
+
+ const succeeded =
+ xhr.status >= 200 && xhr.status < 300 && response && !response.error;
+
+ if (succeeded) {
+ buildRouletteBets(response.bets);
+ addChipsToTable(response.bets);
+ updatePlayerCurrencies(response.gambler);
+ updateResult("Rolls occur every five minutes", "success");
+ } else {
+ updateResult("Unable to place that bet.", "danger");
+ }
+}
+
+if (
+ document.readyState === "complete" ||
+ (document.readyState !== "loading" && !document.documentElement.doScroll)
+) {
+ initializeGame();
+} else {
+ document.addEventListener("load", initializeGame);
+}
diff --git a/files/assets/js/slots_screen.js b/files/assets/js/slots_screen.js
new file mode 100644
index 000000000..859512bc6
--- /dev/null
+++ b/files/assets/js/slots_screen.js
@@ -0,0 +1,67 @@
+function pullSlots() {
+ const { amount, currency } = getWager();
+
+ console.log({amount, currency})
+
+ disableWager();
+ clearResult();
+ document.getElementById("casinoSlotsPull").disabled = true;
+
+ const xhr = new XMLHttpRequest();
+ xhr.open("post", "/casino/slots");
+ xhr.setRequestHeader('xhr', 'xhr');
+ xhr.onload = handleSlotsResponse.bind(null, xhr);
+
+ const form = new FormData();
+ form.append("formkey", formkey());
+ form.append("wager", amount);
+ form.append("currency", currency);
+
+ xhr.send(form);
+}
+
+function handleSlotsResponse(xhr) {
+ let response;
+
+ try {
+ response = JSON.parse(xhr.response);
+ } catch (error) {
+ console.error(error);
+ }
+
+ const succeeded =
+ xhr.status >= 200 && xhr.status < 300 && response && !response.error;
+
+ if (succeeded) {
+ const { game_state, gambler } = response;
+ const state = JSON.parse(game_state);
+ const reels = Array.from(document.querySelectorAll(".slots_reel"));
+ const symbols = state.symbols.split(",");
+
+ for (let i = 0; i < 3; i++) {
+ reels[i].innerHTML = symbols[i];
+ }
+
+ let className;
+
+ if (state.text.includes("Jackpot")) {
+ className = "warning";
+ } else if (state.text.includes("Won")) {
+ className = "success";
+ } else if (state.text.includes("Lost")) {
+ className = "danger";
+ } else {
+ className = "success";
+ }
+
+ updateResult(state.text, className);
+ updatePlayerCurrencies(gambler);
+ reloadFeed()
+ } else {
+ updateResult(response.error, "danger");
+ console.error(response.error);
+ }
+
+ enableWager();
+ document.getElementById("casinoSlotsPull").disabled = false;
+}
diff --git a/files/templates/casino/blackjack_screen.html b/files/templates/casino/blackjack_screen.html
index 6df76fd3f..95dd3500e 100644
--- a/files/templates/casino/blackjack_screen.html
+++ b/files/templates/casino/blackjack_screen.html
@@ -1,232 +1,7 @@
{% extends "casino/game_screen.html" %}
{% block script %}
-
+
{% endblock %}
{% block screen %}
diff --git a/files/templates/casino/game_screen.html b/files/templates/casino/game_screen.html
index 84a3fed69..a2db3a399 100644
--- a/files/templates/casino/game_screen.html
+++ b/files/templates/casino/game_screen.html
@@ -27,209 +27,7 @@
}
-
+
{% block script %} {% endblock %}
diff --git a/files/templates/casino/roulette_screen.html b/files/templates/casino/roulette_screen.html
index 08098c288..58b8da8bc 100644
--- a/files/templates/casino/roulette_screen.html
+++ b/files/templates/casino/roulette_screen.html
@@ -1,395 +1,7 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
-
+
{% endblock %}
{% block screen %}
diff --git a/files/templates/casino/slots_screen.html b/files/templates/casino/slots_screen.html
index daf79e2a2..c10b41f2d 100644
--- a/files/templates/casino/slots_screen.html
+++ b/files/templates/casino/slots_screen.html
@@ -1,75 +1,7 @@
{% extends "casino/game_screen.html" %} {% block result %} N/A {% endblock %}
{% block script %}
-
+
{% endblock %}
{% block screen %}