PDA app: Blackjack (#20803)

Added a blackjack PDA app
This commit is contained in:
ynot01
2023-11-02 01:21:53 -04:00
committed by GitHub
parent 737b323be6
commit a596f0c3db
5 changed files with 503 additions and 2 deletions

View File

@@ -35,3 +35,6 @@
temp.filename = filename
temp.filetype = filetype
return temp
/datum/computer_file/proc/try_insert(obj/item/inserted_item, mob/living/user = null)
return FALSE

View File

@@ -0,0 +1,276 @@
#define BLACKJACK_CONTINUE 0
#define BLACKJACK_LOSS 1
#define BLACKJACK_WIN 2
#define BLACKJACK_TIE 3
#define BLACKJACK_IDLE 4
#define BLACKJACK_DEALER_TURN 5
#define JACK_CARD "J"
#define QUEEN_CARD "Q"
#define KING_CARD "K"
#define ACE_CARD "A"
/datum/computer_file/program/blackjack
filename = "blackjack"
filedesc = "Nanotrasen Micro Arcade: Blackjack"
program_icon_state = "arcade"
extended_desc = "Nanotrasen does not permit anyone under the age of 21 to partake in gambling. Requires an NTNet connection."
requires_ntnet = TRUE
network_destination = "arcade network"
size = 4
tgui_id = "NtosBlackjack"
program_icon = "gamepad"
var/game_state = BLACKJACK_IDLE
/// Card format is value and suit i.e. "2H" = 2 of Hearts, "KC" = King of Clubs
var/list/active_player_cards = list()
var/list/active_dealer_cards = list()
var/list/current_deck = list()
var/credits_stored = 0
var/active_wager = 0
var/set_wager = 5
var/game_end_reason = ""
var/game_id = 0
var/datum/bank_account/temp_account // Call refresh_id_account() or get_credit_amount() before using this
/datum/computer_file/program/blackjack/proc/refresh_id_account()
temp_account = null
if(holder && istype(holder.holder, /obj/item/modular_computer))
var/obj/item/modular_computer/computer = holder.holder
var/obj/item/card/id/stored_card = computer.GetID()
if(istype(stored_card) && istype(stored_card.registered_account))
temp_account = stored_card.registered_account
return stored_card.registered_account
/datum/computer_file/program/blackjack/proc/get_credit_amount()
refresh_id_account()
return temp_account?.account_balance || credits_stored
/datum/computer_file/program/blackjack/proc/adjust_credits(amt)
if(refresh_id_account())
temp_account.adjust_money(amt)
else
credits_stored += amt
/datum/computer_file/program/blackjack/proc/new_game()
if(set_wager <= 0 || get_credit_amount() < set_wager)
return
game_id++
adjust_credits(-set_wager)
active_wager = set_wager
game_state = BLACKJACK_CONTINUE
game_end_reason = ""
active_player_cards = list()
active_dealer_cards = list()
current_deck = list(
"2S","3S","4S","5S","6S","7S","8S","9S","10S","JS","QS","KS","AS",
"2H","3H","4H","5H","6H","7H","8H","9H","10H","JH","QH","KH","AH",
"2C","3C","4C","5C","6C","7C","8C","9C","10C","JC","QC","KC","AC",
"2D","3D","4D","5D","6D","7D","8D","9D","10D","JD","QD","KD","AD"
)
shuffle_inplace(current_deck)
// smooth
play_snd('sound/machines/switch3.ogg')
active_player_cards += pop(current_deck)
active_dealer_cards += pop(current_deck)
active_player_cards += pop(current_deck)
/datum/computer_file/program/blackjack/proc/calculate_hand_worth(list/hand)
var/worth = 0
var/aces = 0
var/list/hand_copy = LAZYCOPY(hand)
for(var/card in hand_copy)
card = copytext(card, 1, -1) // Remove the suit, we do not care about it
if(text2num(card))
worth += text2num(card)
else
switch(card)
if(JACK_CARD, QUEEN_CARD, KING_CARD) // "Picture" cards are worth 10
worth += 10
if(ACE_CARD) // Aces are calculated last
aces += 1
for(var/i in 1 to aces) // Ace is worth either 1 or 11, whichever is more beneficial
if(worth < 12 - aces) // This algorithm will never surpass 21 unless you have too many aces (somehow)
worth += 11
else
worth += 1
return worth
/datum/computer_file/program/blackjack/ui_data(mob/user)
var/list/data = get_header_data()
data["game_state"] = game_state
data["active_dealer_cards"] = active_dealer_cards
data["active_player_cards"] = active_player_cards
data["credits_stored"] = get_credit_amount()
data["local_credits"] = credits_stored
data["has_id"] = !!temp_account
data["active_wager"] = active_wager
data["set_wager"] = set_wager
data["game_end_reason"] = game_end_reason
return data
/datum/computer_file/program/blackjack/try_insert(obj/item/inserted_item, mob/living/user)
if(!holder || !istype(holder?.holder, /obj/item/modular_computer))
return FALSE
if(!inserted_item.get_item_credit_value())
return FALSE
credits_stored += inserted_item.get_item_credit_value()
QDEL_NULL(inserted_item)
play_snd('sound/machines/ping.ogg')
SStgui.try_update_ui(user, src)
return TRUE
/datum/computer_file/program/blackjack/proc/play_snd(sound_path)
if(holder && istype(holder.holder, /obj/item/modular_computer))
var/obj/item/modular_computer/computer = holder.holder
computer.play_computer_sound(sound_path, 40, 1)
/datum/computer_file/program/blackjack/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(..())
return .
if(ishuman(usr))
var/mob/living/carbon/human/user = usr
if(user.age < 21) // We do not condone underage gambling
play_snd('sound/machines/buzz-sigh.ogg')
return FALSE
switch(action)
if("PRG_new_game")
new_game()
return TRUE
if("PRG_eject_credits")
if(credits_stored < 1)
return FALSE
var/obj/item/holochip/holochip = new (usr.drop_location(), credits_stored)
usr.put_in_hands(holochip)
to_chat(usr, span_notice("You withdraw [credits_stored] credits into a holochip."))
credits_stored = 0
play_snd('sound/machines/click.ogg')
return TRUE
if("PRG_set_wager")
if(!isnum(text2num(params["wager"])))
return FALSE
var/new_wager = text2num(params["wager"])
if(round(new_wager) < 1)
return FALSE
set_wager = round(new_wager)
play_snd('sound/machines/click.ogg')
return TRUE
if("PRG_hit")
if(game_state != BLACKJACK_CONTINUE)
return FALSE
play_snd('sound/machines/switch3.ogg')
active_player_cards += pop(current_deck)
check_player_hand(usr)
return TRUE
if("PRG_stand")
if(game_state != BLACKJACK_CONTINUE)
return FALSE
game_state = BLACKJACK_DEALER_TURN
addtimer(CALLBACK(src, PROC_REF(dealer_turn), game_id, usr), 1 SECONDS)
play_snd('sound/machines/click.ogg')
return TRUE
if("PRG_double_down")
if(game_state != BLACKJACK_CONTINUE)
return FALSE
if(get_credit_amount() < active_wager)
return
adjust_credits(-active_wager)
active_wager *= 2
play_snd('sound/machines/switch3.ogg')
active_player_cards += pop(current_deck)
if(!check_player_hand(usr))
game_state = BLACKJACK_DEALER_TURN
addtimer(CALLBACK(src, PROC_REF(dealer_turn), game_id, usr), 1 SECONDS)
return TRUE
/datum/computer_file/program/blackjack/proc/check_player_hand(mob/user)
var/worth = calculate_hand_worth(active_player_cards)
if(worth == 21)
game_state = BLACKJACK_DEALER_TURN
addtimer(CALLBACK(src, PROC_REF(dealer_turn), game_id, user), 1 SECONDS)
return TRUE
if(worth > 21)
game_state = BLACKJACK_LOSS
game_end_reason = "You busted!"
active_wager = 0
play_snd('sound/machines/buzz-two.ogg')
return TRUE
return FALSE
/datum/computer_file/program/blackjack/proc/dealer_turn(g_id, mob/user)
if(game_state != BLACKJACK_DEALER_TURN || game_id != g_id)
return
play_snd('sound/machines/switch1.ogg')
active_dealer_cards += pop(current_deck)
SStgui.try_update_ui(user, src)
var/dealer_worth = calculate_hand_worth(active_dealer_cards)
if(dealer_worth < 17) // Dealer draws until it has 17 or higher
addtimer(CALLBACK(src, PROC_REF(dealer_turn), game_id, user), 1 SECONDS)
return
var/player_worth = calculate_hand_worth(active_player_cards)
// check_player_hand() already checked for player bust
if(dealer_worth == player_worth)
game_state = BLACKJACK_TIE
adjust_credits(active_wager)
active_wager = 0
SStgui.try_update_ui(user, src)
play_snd('sound/machines/button1.ogg')
return
if(dealer_worth > 21 || player_worth > dealer_worth)
if(dealer_worth > 21)
game_end_reason = "Dealer busted!"
else
game_end_reason = "You won!"
game_state = BLACKJACK_WIN
adjust_credits(active_wager * 2)
active_wager = 0
SStgui.try_update_ui(user, src)
play_snd('sound/machines/chime.ogg')
return
if(player_worth < dealer_worth) // this is redundant, but it helps understand what is happening
game_state = BLACKJACK_LOSS // | || || |_
game_end_reason = "You lost!"
active_wager = 0
SStgui.try_update_ui(user, src)
play_snd('sound/machines/buzz-two.ogg')
return
#undef BLACKJACK_CONTINUE
#undef BLACKJACK_LOSS
#undef BLACKJACK_WIN
#undef BLACKJACK_TIE
#undef BLACKJACK_IDLE
#undef BLACKJACK_DEALER_TURN
#undef JACK_CARD
#undef QUEEN_CARD
#undef KING_CARD
#undef ACE_CARD

View File

@@ -121,6 +121,16 @@
return F
return null
/obj/item/computer_hardware/hard_drive/try_insert(obj/item/I, mob/living/user)
if(!holder)
return FALSE
for(var/datum/computer_file/file in stored_files)
if(file.try_insert(I, user))
return TRUE
return FALSE
/obj/item/computer_hardware/hard_drive/Destroy()
for(var/F in stored_files)
qdel(F)

View File

@@ -0,0 +1,211 @@
import { useBackend } from '../backend';
import { Box, Button, Icon, Section, Stack } from '../components';
import { NtosWindow } from '../layouts';
enum GameState {
Continue = 0,
Loss = 1,
Win = 2,
Tie = 3,
Idle = 4,
DealerTurn = 5,
}
const GameStateFlavor = {
0:"Good luck!",
1:"Better luck next time!",
2:"Congratulations!",
3:"Tied!",
4:"Gamble responsibly!",
5:"Dealer is drawing...",
};
const ColorSuit = {
"S":"black",
"H":"red",
"C":"black",
"D":"red",
};
const IconSuit = {
"S":"spa",
"H":"heart",
"C":"cubes",
"D":"diamond",
};
type Data = {
game_state: GameState;
active_dealer_cards: Array<string>;
active_player_cards: Array<string>;
credits_stored: number;
local_credits: number;
has_id: boolean;
active_wager: number;
set_wager: number;
game_end_reason: string;
};
export const NtosBlackjack = (props, context) => {
const { act, data } = useBackend<Data>(context);
return (
<NtosWindow
width={600}
height={572}>
<NtosWindow.Content>
<Stack fill>
<Stack.Item>
<Section fill>
<br />
<br />
<br />
<Button.Confirm
disabled={data.credits_stored < data.set_wager}
onClick={() => act('PRG_new_game')}
textAlign='center'
width={15}
content={"New Game"} />
<br />
<br />
<br />
<Button.Confirm
disabled={data.local_credits < 1}
onClick={() => act('PRG_eject_credits')}
textAlign='center'
width={15}
content={"Eject Credits"} />
<br />
<Button.Input
currentValue={data.set_wager}
onCommit={(e:any, value:string) => act('PRG_set_wager', {
wager: value,
})}
textAlign='center'
width={15}
content={"Set Wager"} />
<br />
<br />
<br />
<Button.Confirm
disabled={data.game_state !== GameState.Continue}
onClick={() => act('PRG_hit')}
textAlign='center'
width={15}
content={"Hit"} />
<br />
<Button.Confirm
disabled={data.game_state !== GameState.Continue}
onClick={() => act('PRG_stand')}
textAlign='center'
width={15}
content={"Stand"} />
<br />
<Button.Confirm
disabled={data.game_state !== GameState.Continue || data.credits_stored < data.set_wager}
onClick={() => act('PRG_double_down')}
textAlign='center'
width={15}
content={"Double Down"} />
<br />
<br />
<br />
<Box textAlign='center'>
Credits {data.has_id ? " in account" : ""}
<br />
{data.credits_stored}
<br />
<br />
Active Bet
<br />
{data.active_wager}
<br />
<br />
<br />
<br />
{data.game_end_reason}
<br />
{GameStateFlavor[data.game_state]}
</Box>
</Section>
</Stack.Item>
<Stack.Item grow>
<Stack fill vertical>
<Stack.Item grow>
<Section fill>
<Box textAlign='center'>
Dealer&apos;s hand
</Box>
<br />
<Stack>
{data.active_dealer_cards.map((card_string, index) =>
(<Stack.Item
bold
fontSize={1.4}
height={7}
width={5}
backgroundColor='white'
color={ColorSuit[card_string.slice(-1)]}
key={'dealercard'+index}>
<Stack fill vertical>
<Stack.Item>
<Box px={0.4} fill>
{card_string.slice(0, -1)}
</Box>
</Stack.Item>
<Stack.Item grow align='center'>
<Icon name={IconSuit[card_string.slice(-1)]} />
</Stack.Item>
<Stack.Item align='end'>
<Box px={0.6} fill textAlign='right'>
{card_string.slice(0, -1)}
</Box>
</Stack.Item>
</Stack>
</Stack.Item>)
)}
</Stack>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section fill>
<Box textAlign='center'>
Your hand
</Box>
<br />
<Stack>
{data.active_player_cards.map((card_string, index) =>
(<Stack.Item
bold
fontSize={1.4}
height={7}
width={5}
backgroundColor='white'
color={ColorSuit[card_string.slice(-1)]}
key={'playercard'+index}>
<Stack fill vertical>
<Stack.Item>
<Box px={0.4} fill>
{card_string.slice(0, -1)}
</Box>
</Stack.Item>
<Stack.Item grow align='center'>
<Icon name={IconSuit[card_string.slice(-1)]} />
</Stack.Item>
<Stack.Item align='end'>
<Box px={0.6} fill textAlign='right'>
{card_string.slice(0, -1)}
</Box>
</Stack.Item>
</Stack>
</Stack.Item>)
)}
</Stack>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
</Stack>
</NtosWindow.Content>
</NtosWindow>
);
};

View File

@@ -3022,6 +3022,7 @@
#include "code\modules\modular_computers\file_system\program.dm"
#include "code\modules\modular_computers\file_system\program_events.dm"
#include "code\modules\modular_computers\file_system\programs\arcade.dm"
#include "code\modules\modular_computers\file_system\programs\blackjack.dm"
#include "code\modules\modular_computers\file_system\programs\configurator.dm"
#include "code\modules\modular_computers\file_system\programs\file_browser.dm"
#include "code\modules\modular_computers\file_system\programs\minesweeper.dm"