Card-based skill checks

The time has come for me to add skill checks to my game. I still have no idea how to implement them. I know how I want them to work, but I have no idea how to explain the idea to Inform 7.

How I want this to work is that the player has a deck cards with numbered values 2-14. If the top 4 can be labeled “J,” “Q,” “K,” and “A,” then bonus, but if they’re labeled numerically, that’s fine, too. The player initially has a hand of seven cards, and when they attempt to do something that requires a skill check, their parser switches to a display of the seven cards currently in their hand. When this happens, they are unable to do anything until they have selected a card in their hand to play or chosen to redraw.

If they select a card, the card is compared to a difficulty number, which can be either hard-coded or random depending on the situation. If it’s equal to or greater than the difficulty number, the card they played is discarded, they’re told what the difficulty number was, and the story continues.

If the value of the card they played is less than the difficulty number, then they may be able to back out, but not always, and may always play another card or redraw. If they back out, then they lose their first card and exit the skill check mode without having made progress, which might be a good idea if the thing they’re doing isn’t actually necessary.

If they play a second card, its value is added to the first and compared to the difficulty again. There’s no limit to how many cards you can put down at once, so the goal is to learn the difficulties of the game to the point where you can avoid overshooting. If the difficulty is six, then playing an actual six and playing a two and a four are pretty much the same, but playing a two, a three, and a four for nine total means you’ve wasted a bunch of extra points.

If the player chooses to redraw, then they discard all remaining cards, their hand size decreases by one, and they redraw cards up to their maximum. If their maximum hand size ever hits zero, that’s game over.

Then there’s meta-effects. It should be possible to raise the hand maximum back up again outside of combat by consuming limited resources (the most stereotypical fantasy example would be drinking a health potion), but not to raise it past the original maximum of seven…except sometimes you do want to raise it past the original maximum of seven (the stereotypical fantasy example would be drinking a potion of giant strength). It should also be possible to have skill bonuses to certain types of challenges, so that you can have +1 sneaking and -1 nuclear engineering, so that a card whose printed value is 8 is worth 9 in a sneaking challenge by 7 in a nuclear engineering challenge. The skill bonus should also be displayed in the parser.

In play, it’s as simple as looking at the seven or less options in the parser, guessing what the number is based on your knowledge of what other difficulties the game has had, and playing cards to try and hit that number. But scripting it is going to be a huge deal and I don’t even know where to start.

This thread is now my devblog as I aimlessly build components of this system and hope they will fit together despite my lack of an overarching structure. What’s working is that I can deal and redraw cards and they will show up in the parser appropriately:

Drawing cards
Section 1 - Cards

A person has a number called will. The will of the player is 7.

A person has a number called focus. The focus of the player is 7.

Suit is a kind of value. The suits are clubs, diamonds, hearts, spades, and suitless.

Table of Cards
Topic	Card	Abbrev	Value	Suit
"Two/Deuce/2"	"Two"	"2"	2	Hearts
"Three/3"	"Three"	"3"	3	Hearts
"Four/4"	"Four"	"4"	4	Hearts
"Five/5"	"Five"	"5"	5	Hearts
"Six/6"	"Six"	"6"	6	Hearts
"Seven/7"	"Seven"	"7"	7	Hearts
"Eight/8"	"Eight"	"8"	8	Hearts
"Nine/9"	"Nine"	"9"	9	Hearts
"Ten/10"	"Ten"	"10"	10	Hearts
"Jack/J"	"Jack"	"J"	11	Hearts
"Queen/Q"	"Queen"	"Q"	12	Hearts
"King/K"	"King"	"K"	13	Hearts
"Ace/A"	"Ace"	"A"	14	Hearts

Table of the Player's Hand
Topic	Card	Abbrev	Value	Suit
"-"	some text	some text	a number	a suit
with 13 blank rows

Dealing is an action applying to nothing. Understand "deal" as dealing.

Table of an Empty Hand
Topic	Card	Abbrev	Value	Suit
"-"	some text	some text	a number	a suit

Carry out dealing:
	sort the Table of Cards in random order;
	repeat with N running from 1 to the focus of the player:
		now the topic in row N of the Table of the Player's Hand is the topic in row N of the Table of Cards;
		now the card in row N of the Table of the Player's Hand is the card in row N of the Table of Cards;
		now the abbrev  in row N of the Table of the Player's Hand is the abbrev in row N of the Table of Cards;
		now the value in row N of the Table of the Player's Hand is the value in row N of the Table of Cards;
		now the suit in row N of the Table of the Player's Hand is the suit in row N of the Table of Cards;
	repeat with N running from the focus of the player plus one to the number of rows in the Table of the Player's Hand:
		now the topic in row N of the Table of the Player's Hand is the topic in row 1 of the Table of an Empty Hand;
		now the card in row N of the Table of the Player's Hand is "None";
		now the abbrev in row N of the Table of the Player's Hand is "-";
		now the value in row N of the Table of the Player's Hand is 0;
		now the suit in row N of the Table of the Player's Hand is suitless.

Shuffling is an action applying to nothing. Understand "redraw" as shuffling.

Carry out shuffling:
	sort the Table of Cards in random order;
	repeat with N running from 1 to the focus of the player:
		now the card in row N of the Table of the Player's Hand is the card in row N of the Table of Cards;
		now the abbrev  in row N of the Table of the Player's Hand is the abbrev in row N of the Table of Cards;
		now the value in row N of the Table of the Player's Hand is the value in row N of the Table of Cards;
		now the suit in row N of the Table of the Player's Hand is the suit in row N of the Table of Cards;
	decrease the focus of the player by 1;
	repeat with N running from the focus of the player plus one to the number of rows in the Table of the Player's Hand:
		now the card in row N of the Table of the Player's Hand is "None";
		now the abbrev in row N of the Table of the Player's Hand is "-";
		now the value in row N of the Table of the Player's Hand is 0;
		now the suit in row N of the Table of the Player's Hand is suitless.

I haven’t tried it yet, but it should even be easy to make actions that restore the player’s focus up to their current will, thus refreshing their deck, and even to make actions that affect the player’s will, thus changing how many cards they’re reset to when they refresh. The cards also list suits, so that I can expand things out to a full deck of 52 playing cards later if I want to. I’m considering having a system where playing a pair or a straight or something gives some kind of bonus, but right now the suit does nothing.

Here’s as far as I’ve gotten into playing cards:

Section 2 - Encounters

Encounter is a scene. Encounter begins when play begins.

Every turn during Encounter:
	now the command prompt is "Play a card: [abbrev in row 1 of the Table of the Player's Hand] [abbrev in row 2 of the Table of the Player's Hand] [abbrev in row 3 of the Table of the Player's Hand] [abbrev in row 4 of the Table of the Player's Hand] [abbrev in row 5 of the table of the Player's Hand] [abbrev in row 6 of the Table of the Player's Hand] [abbrev in row 7 of the Table of the Player's Hand]>"

Don’t even need to put that in a “Hide Details” box. There’s a topic column in the Table of the Player’s Hand, but I have no idea how to make an action that refers to a topic. It seems to be possible, because the examples in the documentation use built-in verbs like “ask the Sybil about a topic listed in the Table of Sybil’s Replies,” but code like this isn’t working:

After reading a command during Encounter:
	if the player's command includes a topic listed in the Table of the Player's Hand:
		say "You played a card!";
	otherwise:
		say "You don't have that card in your hand. And also it might not even be a card. Honestly, my ability to comprehend things which are not cards in your hand is very limited right now."

Maybe there’s a way to unload table data into values that can work with regular actions?

One cool thing about Inform 7 is you can actually implement physical cards - whether the player interacts with them or not. You can have an off-stage container called “the deck” and and off-stage container called “your hand”.

You can write things like “now a random card in the deck is in your hand” and “if the five of clubs is in your hand”…etc.

1 Like

Is there a way to display the cards contained in the hand in the parser?

I have an object-based card-dealing script half-written, but I can’t seem to affect the cards unless I’m in the same room as them. The following code did not help:

Encounter is a scene. Encounter begins when play begins.
	
After deciding the scope of the player during Encounter:
	place backstage in scope;
	place the contents of the hand in scope.

I’m also getting a weird bug where entering the name of any card makes it impossible to send commands to Inform, the parser just treats the enter-key as meaning line break. I know from commenting out bits of code that this code is the problem, but I have no idea why:

After reading a command:
	if the player's command includes "of [suit]":
		while the player's command includes "of":
			cut the matched text;
	repeat through the Table of Card Names:
		while the player's command includes topic entry:
			replace the matched text with "[value entry]".

EDIT: The second bug was due to how the topic was set up. I was able to fix it. The first one is still a problem.

The Game Room is a room.

Your hand is a container.

The deck is a container.

A card is a kind of thing. An ace is a card in the deck. A king is a card in the deck. A queen is a card in the deck. A joker is a card in the deck. 

A four of clubs is a card in your hand. A three of diamonds is a card in your hand.

Report taking inventory:
	say "In your hand of cards is [a list of cards in your hand]."

Every turn:
	now a random card in the deck is in your hand;
	now a random card in your hand is in the deck;
Result:

i

You are carrying nothing.

In your hand of cards is a four of clubs and a three of diamonds.

i

You are carrying nothing.

In your hand of cards is a queen and a three of diamonds.

i

You are carrying nothing.

In your hand of cards is a joker and a queen

This might be useful; it’s an implementation of Autumn Leaves (a solitaire card game) in Inform 7, which uses physical objects to represent cards and only lets players refer to them when they’re face-up.

3 Likes

I have a mostly object based version that’s mostly working, but I still can’t find a way to get the contents of the hand into the parser without relying on the table. The problem there is, I don’t know how to update the table when a card is played in order to update the parser. I’d like to avoid just using the inventory to keep track of the hand if possible. Having the hand constantly in front of the player helps streamline the system and makes it more intuitive.

EDIT: Here is what I have right now, for reference. Warning: Big.

Big
Section 1 - Card Stats and Conditions

A person has a number called will. The will of the player is 7.

A person has a number called focus. The focus of the player is 7.

A person has a number called work. The work of the player is 0.

A person has a number called difficulty. The difficulty of the player is 0.

A person has a number called success. The success of the player is 0.

A person can be either safe or imperiled. The player is safe.

A person can be either engaged or unengaged. The player is unengaged.

A person can be either cornered or uncornered. The player is uncornered.

[The WILL is the maximum hand draw the player is restored to when they are refreshed by a consumable or by visiting a refreshing location.

The FOCUS is the maximum hand draw the player has right now. When an encounter begins, they draw this many cards, and when they redraw, their focus drops by one.

The WORK is the total point value from all played cards in the current engagement.

The DIFFICULTY is the current amount of points the player must exceed to complete the current engagement.

While SAFE, the player has no cards. While IMPERILED, the player has cards. The player gets cards equal to their focus whenever they become imperiled, so most perilous encounters should consist of multiple different engagements or else one really big engagement.

While ENGAGED, the player cannot move, and instead can only play cards, redraw, or retreat. When UNENGAGED, the player can move normally. The player should never be engaged without being imperiled.

When CORNERED, the player cannot retreat. When uncornered, they can retreat normally. The player should never be cornered without being engaged.]

Suit is a kind of value. The suits are widows, spiders, tears, bats, and suitless. Understand "widow" as widows. Understand "spider" as spiders. Understand "bat" as bats. Understand "tear" as tears.

A card is a kind of thing. A card can be written or unwritten. A card is usually unwritten. A card has a suit. A card has a number called rank. Understand the suit property as describing a card. Understand the rank property as describing a card.

To say (count - a number) as a card name:
	choose row count minus one in the Table of Card Names;
	say "[card name entry]".

Rule for printing the name of a card (called target):
	say "[rank of the target as a card name] of [suit of the target]"

Table of Card Names
Topic	Card Name	Abbrev	Value	Written Value	Suit
"Two/Deuce"	"Two"	"2"	2	"2"	Widows
"Three"	"Three"	"3"	3	"3"	Widows
"Four"	"Four"	"4"	4	"4"	Widows
"Five"	"Five"	"5"	5	"5"	Widows
"Six"	"Six"	"6"	6	"6"	Widows
"Seven"	"Seven"	"7"	7	"7"	Widows
"Eight"	"Eight"	"8"	8	"8"	Widows
"Nine"	"Nine"	"9"	9	"9"	Widows
"Ten"	"Ten"	"10"	10	"10"	Widows
"Jack/J"	"Jack"	"J"	11	"11"	Widows
"Queen/Q"	"Queen"	"Q"	12	"12"	Widows
"King/K"	"King"	"K"	13	"13"	Widows
"Ace/A"	"Ace"	"A"	14	"14"	Widows

Table of the Player's Hand
Topic	Card Name	Abbrev	Value	Suit
"-"	some text	some text	a number	a suit
with 13 blank rows

Table of an Empty Hand
Topic	Card Name	Abbrev	Value	Suit
"-"	some text	some text	a number	a suit

Section 2 - Drawing Cards

Redrawing is an action applying to nothing. Understand "redraw" as redrawing.

Carry out redrawing:
	now every card is in the discard;
	now every card is unwritten;
	sort the Table of Card Names in random order;
	repeat with N running from 1 to the focus of the player:
		now the card name in row N of the Table of the Player's Hand is the card name in row N of the Table of Card Names;
		now the abbrev in row N of the Table of the Player's Hand is the abbrev in row N of the Table of Card Names;
		now the value in row N of the Table of the Player's Hand is the value in row N of the Table of Card Names;
		now the suit in row N of the Table of the Player's Hand is the suit in row N of the Table of Card Names;
	decrease the focus of the player by 1;
	repeat with N running from the focus of the player plus one to the number of rows in the Table of the Player's Hand:
		now the card name in row N of the Table of the Player's Hand is "None";
		now the abbrev in row N of the Table of the Player's Hand is "-";
		now the value in row N of the Table of the Player's Hand is 0;
		now the suit in row N of the Table of the Player's Hand is suitless;
	sort the Table of Card Names in value order;
	repeat with N running from 1 to the focus of the player:
		let C be a random card that is unwritten;
		now the suit of C is the suit in row N of the Table of the Player's Hand;
		now the rank of C is the value in row N of the Table of the Player's Hand;
		now the player has C;
		now C is written.

Section 3 - Playing Cards

Encounter is a recurring scene. Encounter begins when the player is engaged. Encounter ends when the player is safe.

When Encounter begins:
	now every card is in the discard;
	now every card is unwritten;
	sort the Table of Card Names in random order;
	repeat with N running from 1 to the focus of the player:
		now the topic in row N of the Table of the Player's Hand is the topic in row N of the Table of Card Names;
		now the card name in row N of the Table of the Player's Hand is the card name in row N of the Table of Card Names;
		now the abbrev  in row N of the Table of the Player's Hand is the abbrev in row N of the Table of Card Names;
		now the value in row N of the Table of the Player's Hand is the value in row N of the Table of Card Names;
		now the suit in row N of the Table of the Player's Hand is the suit in row N of the Table of Card Names;
	repeat with N running from the focus of the player plus one to the number of rows in the Table of the Player's Hand:
		now the topic in row N of the Table of the Player's Hand is the topic in row 1 of the Table of an Empty Hand;
		now the card name in row N of the Table of the Player's Hand is "None";
		now the abbrev in row N of the Table of the Player's Hand is "-";
		now the value in row N of the Table of the Player's Hand is 0;
		now the suit in row N of the Table of the Player's Hand is suitless;
	sort the Table of Card Names in value order;
	repeat with N running from 1 to the focus of the player:
		let C be a random card that is unwritten;
		now the suit of C is the suit in row N of the Table of the Player's Hand;
		now the rank of C is the value in row N of the Table of the Player's Hand;
		now the player has C;
		now C is written.

Every turn during Encounter:
	now the command prompt is "Play a card: [abbrev in row 1 of the Table of the Player's Hand] [abbrev in row 2 of the Table of the Player's Hand] [abbrev in row 3 of the Table of the Player's Hand] [abbrev in row 4 of the Table of the Player's Hand] [abbrev in row 5 of the table of the Player's Hand] [abbrev in row 6 of the Table of the Player's Hand] [abbrev in row 7 of the Table of the Player's Hand]>";
	if the work of the player is greater than the difficulty of the player:
		say "Success! The difficulty of this challenge was [difficulty of the player], and you put down [work of the player] points from your cards.";
		now the player is unengaged;
		increase the success of the player by 1;
		now the difficulty of the player is 0;
		now the work of the player is 0.

After reading a command while the player is imperiled:
	if the player's command includes "of [suit]":
		while the player's command includes "of":
			cut the matched text;
	repeat through the Table of Card Names:
		while the player's command includes topic entry:
			replace the matched text with written value entry.

After reading a command while the player is engaged:
	if the player's command does not match "play [card]":
		if the player's command does not match "redraw":
			If the player's command does not match "retreat":
				say "Redraw, retreat, or play a card.";
				reject the player's command.

Instead of dropping a card, say "Best to hang onto that until you need it."

When Encounter ends:
	now the command prompt is ">";
	say "You have succeeded!";
	now every card is in the discard;
	now every card is unwritten;
	now the player is uncornered;
	now the player is unengaged.

Playing a card is an action applying to one thing. Understand "play [card]" as playing a card.

Before playing a card:
	if the player does not have the noun:
		say "You don't have that card in your hand.";
		reject the player's command;
	if the player is not engaged:
		say "There's no immediate obstacle to play the card against. Hold onto your cards until you find one.";
		reject the player's command.

Carry out playing a card:
	now the noun is in the discard;
	increase the work of the player by the rank of the noun;
	if there is a topic corresponding to a value of the rank of the noun in the Table of the Player's Hand:
		now the topic corresponding to the value of the rank of the noun in the Table of the Player's Hand is the topic in row 1 of the Table of an Empty Hand;
		now the card name corresponding to the value of the rank of the noun in the Table of the Player's Hand is "None";
		now the abbrev corresponding to the value of the rank of the noun in the Table of the Player's Hand is "-";
		now the suit corresponding to the value of the rank of the noun in the Table of the Player's Hand is suitless;
		now the value corresponding to the value of the rank of the noun in the Table of the Player's Hand is 0.

Report playing a card:
	say "You play [the noun]."

Retreating is an action applying to nothing. Understand "retreat" as retreating.

Check retreating:
	if the player is cornered:
		say "You can't back out of this one." instead.

Check retreating:
	if the player is unengaged:
		say "There's nothing to retreat from." instead.

Carry out retreating:
	say "You abandon your efforts.";
	now the work of the player is 0;
	now the player is unengaged.

Section 4 - Miscellanea

The test chamber is a room. "Current work is [the work of the player]."

Backstage is east of the test chamber. The discard is a container in Backstage. The deck is a container in Backstage. The hand is a container in Backstage. It is openable and open. There are 13 cards in the discard.

A spider is an animal. It is in Backstage.

Instead of attacking a spider:
	say "The spider becomes angry!";
	now the difficulty of the player is 8;
	now the player is imperiled;
	now the player is engaged.
	
After reading a command when the success of the player is 3:
	now the spider is nowhere;
	now the tricky spider is in Backstage.

A tricky spider is an animal. It is nowhere.

Instead of attacking a tricky spider:
	say "The tricky spider holds you in place with webs!";
	now the difficulty of the player is 12;
	now the player is imperiled;
	now the player is engaged;
	now the player is cornered.
	
After reading a command when the success of the player is 4:
	if the tricky spider is in Backstage:
		say "You have slain the tricky spider!";
		now the tricky spider is nowhere;
		now the player is safe;
		now the player is unengaged;
		now the player is uncornered;
		reject the player's command.

This runs, you can go east, kick the spider until the tricky spider shows up, then kick the tricky spider at which point you win. There’s no victory screen or anything, you just wander around purgatory forever, you spider-kicking monster, but as a proof of concept, it all works except that when playing a card it only checks for a matching rank, not suit. I can’t figure out how to get it to check the table for something that matches both rank and suit, and I can’t figure out how to get the hand into the parser without using a table. I could give each card in the table a unique ID and then have it copy that unique ID onto the card objects along with the rank and the suit, I guess, but it’d be a lot less effort to just search the table for entries corresponding to both a value and a suit, since every card does have a unique rank/suit combination and each rank has a unique value.

More power to you if you like using tables - and most people do, I’m weirdly table-averse if I can help it. That’s why I am partial to the idea of simulating the cards physically, even if out-of-world because you can manipulate them like real cards rather than doing table-row reads, and also add properties like “a card has a number that varies called rank” and “cardsuit is a kind of value. The cardsuits are hearts, diamonds, clubs, and spades. Every card has a cardsuit.” Then you can do things like “If the cardsuit of a random card enclosed by the player is diamonds…”

(Not that I’m trying to convince you to change your system - I’m just fascinated by I7’s simulation capabilities.)

I’ve found Inform’s tables really hard to work with and prefer using objects, but I can’t figure out any way to get a summary of the cards into the command prompt while using objects, so I have a table that sends information to the prompt and is also used to import rank and suit values onto the card objects. And I still can’t figure out how to tell Inform to find a row matching both rank and suit without having to create a whole new column with a list of unique IDs.

The only way to do this is to repeat through the table, stopping when your condition is fulfilled. There’s no built-in phrase to do this.

Unique IDs is also a reasonable approach.

1 Like

I’ve already got a working version with unique IDs, so it’s not a big deal either way, but I tried for like an hour to get checking for two separate conditions to work and wasn’t able to. How do you do this?

I mean this sort of thing:

Table of Coordinates
x-axis	y-axis	label
1	3	"One three"
1	4	"One four"
1	6	"One six"
2	2	"Two two"
2	5	"Two five"
2	6	"Two six"

To decide what text is the label corresponding to (X - number) and (Y - number):
	repeat through the table of coordinates:
		if the x-axis entry is X and the y-axis entry is Y:
			decide on the label entry;
	decide on "Nothing"

When play begins:
	let L1 be the label corresponding to 1 and 6;
	let L2 be the label corresponding to 2 and 5;
	say "[L1]. [L2].";

You should be able to do something like this:

Every turn during Encounter:
	now the command prompt is "Play a card: [list of cards held by the player]>";

(I don’t know if you saw my post in the other thread but there’s some stuff in there about implementing cards as objects. I mentioned the documentation example “Tilt 1” but from some of your code here I’m guessing you’ve already seen it?)

(Note that you don’t have to do that every turn: the text will get re-evaluated every time, so you can just set it once, and it’ll keep changing on its own from there on out.)

1 Like

I did end up using a lot of Tilt, yeah. And as a related question, I copy/pasted this almost exactly from Tilt:

After reading a command:
	if the player's command includes "of [suit]":
		while the player's command includes "of":
			cut the matched text;
	repeat through the Table of Card Names:
		while the player's command includes topic entry:
			replace the matched text with written value entry.

It worked for a while, but something seems to have broken it and I don’t know what. Inform no longer recognizes Jack, Ace, etc. Does anyone have any idea why this might have stopped working?

EDIT: I have discovered several incorrect ways of solving this problem. It’s not because of the table being set up wrong. Even when mapped to a table identical to the one found in Tilt, it still doesn’t work.

It’s definitely something to do with my code, specifically, because copying Tilt exactly with nothing else works fine.

It’s not because of some kind of conflict with other “After reading a command” code, because there’s only one other part of the code that uses that syntax and the problem persists even when that part is commented out.

Wherever the problem is, it exists in the encounter code, because it persists even in the encounter demo project I created to test the code in isolation from the rest of my story.

It’s not a problem with recognizing the existence of face cards at all. Trying to play an 11 works just fine, but trying to play a jack does not.

EDIT 2: It’s also not a problem with Inform failing to read the entire block of code entirely. Editing the code to this:

After reading a command while the player is imperiled:
	if the player's command includes "of [suit]":
		while the player's command includes "of":
			cut the matched text;
	repeat through the Table of Card Names:
		while the player's command includes Topic entry:
			replace the matched text with Written Value entry;
	if the player's command includes "fizzbop":
		showme the contents of the Table of Card Names.

Causes the story to show the contents of the table properly when “fizzbop” is entered.

It’s not a problem with the table or the way that specific code block is written. Copying the table and the code block from Tilt exactly does not solve the problem.

So Inform knows that “play 11” means to play the jack. It’s reading this code block. The table is formatted properly and the entries we’re looking for all match. But for some reason, it’s not replacing “play jack” with “play 11.”

EDIT 3: Copy/pasting the table from Tilt didn’t work the first time, but while flailing about hoping to get insight into what the bug actually was, I tried it again and now it works. So, problem solved, but also, huh?

I’m kind of going to recommend that you change your system here, because it seems like some of the things you’re having trouble with would be easier if you implement the cards as objects with properties instead of using tables. And tables are pretty annoying, as you’re finding–plus they’re creating large blocks of identical code, which is dangerous, because if you change something in one place you have to make sure to change it everywhere.

Specifically: when you’re talking about getting the card names from the parser, that’s something you can do fairly readily using the properties of the cards and using synonyms. And when you want to list the cards, that’s something you can do by repeating through the cards the player has and writing a routine to translate the card into a short form.

Another thing is that in general you want to avoid processing commands using “After reading a command” rules. It’s usually less fragile to use the parser and action machinery. For this code I even found a way to avoid the After reading a command rule that deletes “of,” by just defining “of spiders” as a synonym for spiders, etc. (This would be something that you might not want to do if you had lots and lots of suits, because you have to type these synonyms for hand by each suit, but with five suits it seems doable.)

That said, I had to tweak this a bit, though part of it was because one time I straight up forgot to add “Jack” as a synonym for jacks. Also the behavior of “play A Bats” is a bit strange, because Inform wants to interpret “A” as the indefinite article. That might be hard to work around.

Anyway, here’s what I have, not a table in sight:

click here for long code thing
Section 1 - Card Stats and Conditions

A person has a number called will. The will of the player is 7.

A person has a number called focus. The focus of the player is 7.

A person has a number called work. The work of the player is 0.

A person has a number called difficulty. The difficulty of the player is 0.

A person has a number called success. The success of the player is 0.

A person can be either safe or imperiled. The player is safe.

A person can be either engaged or unengaged. The player is unengaged.

A person can be either cornered or uncornered. The player is uncornered.

[The WILL is the maximum hand draw the player is restored to when they are refreshed by a consumable or by visiting a refreshing location.

The FOCUS is the maximum hand draw the player has right now. When an encounter begins, they draw this many cards, and when they redraw, their focus drops by one.

The WORK is the total point value from all played cards in the current engagement.

The DIFFICULTY is the current amount of points the player must exceed to complete the current engagement.

While SAFE, the player has no cards. While IMPERILED, the player has cards. The player gets cards equal to their focus whenever they become imperiled, so most perilous encounters should consist of multiple different engagements or else one really big engagement.

While ENGAGED, the player cannot move, and instead can only play cards, redraw, or retreat. When UNENGAGED, the player can move normally. The player should never be engaged without being imperiled.

When CORNERED, the player cannot retreat. When uncornered, they can retreat normally. The player should never be cornered without being engaged.]

Suit is a kind of value. The suits are widows, spiders, tears, bats, and suitless. Understand "widow" and "of widows" and "Wi" as widows. Understand "spider" and "of spiders" and "Sp" as spiders. Understand "bat" and "of bats" and "Ba" as bats. Understand "tear" and "of tears" and "Te" as tears. Understand "Su" and "of suitless" as suitless.

A suit has some text called the abbreviation. The abbreviation of widows is "Wi". The abbreviation of spiders is "Sp". The abbreviation of tears is "Te". The abbreviation of bats is "Ba". The abbreviation of suitless is "Su".

A card is a kind of thing. A card can be written or unwritten. A card is usually unwritten. A card has a suit. A card has a number called rank. Understand the suit property as describing a card. Understand the rank property as describing a card.

Understand "A/Ace" as a card when the rank of the item described is 14. Understand "K/King" as a card when the rank of the item described is 13. Understand "Q/Queen" as a card when the rank of the item described is 12. Understand "J/Jack" as a card when the rank of the item described is 11.

To say (count - number) as a card name:
	let T be some text;
	if count is:
		-- 14: now T is "Ace";
		-- 11: now T is  "Jack";
		-- 12: now T is "Queen";
		-- 13: now T is "King";
		-- otherwise: now T is "[count in words]";
	say T in sentence case. ["sentence case" capitalizes the first letter]

To say (count - a number) as an abbreviated card name:
	if count is:
		-- 14: say "A";
		-- 11: say "J";
		-- 12: say "Q";
		-- 13: say "K";
		-- otherwise: say "[count]". 

		
Rule for printing the name of a card (called target):
	say "[rank of the target as a card name] of [suit of the target]".
	
Identity relates a card (called X) to a card (called Y) when the rank of X is the rank of Y and the suit of X is the suit of Y. The verb to be identical to means the identity relation. [will be used to prevent making two identical cards]

Section 2 - Drawing Cards

Redrawing is an action applying to nothing. Understand "redraw" as redrawing.

Carry out redrawing:
	now every card is in the discard;
	now every card is unwritten;
	repeat with N running from 1 to the focus of the player:
		if there is a card (called the draw) in the discard:
			initialize the draw;
			now the player has the draw;
		otherwise:
			say "The discard has been exhausted.";
			break. [this stops the loop so the message doesn't repeat--not that it should be possible to get here anyway!]
		
To initialize (flop - a card):
	now the suit of flop is a random suit;
	now the rank of the flop is a random number between 2 and 14;
	while the flop is identical to a written card:
		now the suit of the flop is a random suit;
		now the rank of the flop is a random number between 2 and 14; [we will have to be careful not to let the number of cards in the game get anywhere near the total number of possible cards, or this could take a long time or even go into an infinite loop. But if that's an issue, we should just generate all 65 possible cards at the beginning.]
	now the flop is written.

Section 3 - Playing Cards

Encounter is a recurring scene. Encounter begins when the player is engaged. Encounter ends when the player is safe.

When Encounter begins:
	try redrawing.

Every turn during Encounter:
	now the command prompt is "Play a card: [abbreviated description of the player's hand]>";
	if the work of the player is greater than the difficulty of the player:
		say "Success! The difficulty of this challenge was [difficulty of the player], and you put down [work of the player] points from your cards.";
		now the player is unengaged;
		now the player is safe;
		increase the success of the player by 1;
		now the difficulty of the player is 0;
		now the work of the player is 0.

To say abbreviated description of the player's hand:
	repeat with flop running through cards held by the player:
		say "[rank of flop as an abbreviated card name] [abbreviation of suit of flop] ".

Before doing anything other than redrawing or retreating or playing a card while the player is engaged:
	say "Redraw, retreat, or play a card." instead.

Instead of dropping a card, say "Best to hang onto that until you need it."

When Encounter ends:
	now the command prompt is ">";
	say "You have succeeded!";
	now every card is in the discard;
	now every card is unwritten;
	now the player is uncornered;
	now the player is unengaged.

Playing a card is an action applying to one thing. Understand "play [card]" as playing a card.

Check playing a card:
	if the player does not have the noun:
		say "You don't have that card in your hand." instead;
	if the player is not engaged:
		say "There's no immediate obstacle to play the card against. Hold onto your cards until you find one." instead.

Carry out playing a card:
	now the noun is in the discard;
	increase the work of the player by the rank of the noun.

Report playing a card:
	say "You play [the noun]."

Retreating is an action applying to nothing. Understand "retreat" as retreating.

Check retreating:
	if the player is cornered:
		say "You can't back out of this one." instead.

Check retreating:
	if the player is unengaged:
		say "There's nothing to retreat from." instead.

Carry out retreating:
	say "You abandon your efforts.";
	now the work of the player is 0;
	now the player is unengaged.

Section 4 - Miscellanea

The test chamber is a room. "Current work is [the work of the player]."

Backstage is east of the test chamber. The discard is a container in Backstage. The deck is a container in Backstage. The hand is a container in Backstage. It is openable and open. There are 13 cards in the discard.

A spider is an animal. It is in Backstage.

Instead of attacking a spider:
	say "The spider becomes angry!";
	now the difficulty of the player is 8;
	now the player is imperiled;
	now the player is engaged.
	
Every turn when the success of the player is 3 and the spider is in Backstage:
	now the spider is nowhere;
	now the tricky spider is in Backstage.

A tricky spider is an animal. It is nowhere.

Instead of attacking a tricky spider:
	say "The tricky spider holds you in place with webs!";
	now the difficulty of the player is 12;
	now the player is imperiled;
	now the player is engaged;
	now the player is cornered.
	
Every turn when the success of the player is 4:
	if the tricky spider is in Backstage:
		say "You have slain the tricky spider!";
		now the tricky spider is nowhere;
		now the player is safe;
		now the player is unengaged;
		now the player is uncornered;
		reject the player's command.
1 Like

Is there a way to make blank cards show up in the command prompt with this method? One thing I like about the way it works now is that when you play a card, it leaves behind a dash mark, and then when you redraw, the final card spot also has a dash mark. This makes it intuitive that using redraw causes the total hand size to shrink by one, whereas just printing the number of cards requires the player to notice that the total hand size is shrinking, something which doesn’t become obvious until you’ve already redrawn down to five or four cards or so.

Right now, the system requires no tutorializing at all. The command prompt switches to a hand of cards and the player is told to play one, so they PLAY JACK or whatever, and it works. Then they notice that the report is “You play the Jack of Widows,” so later on when the suit of Bats is added, they won’t be confused by 10-Wi and J-Bat. All they need to be told is that the suit of Bats has been added, and then they can work out that “Bat” is short for the Bats suit and therefore “Wi” must be short for the Widows suit they’ve been using all along. I’m really eager to get rid of this tottering pile of tables I’ve got duct-taped together right now, but it’s important to me that this system remain something a player can fairly easily figure out by playing, without me having to stop and dump a tutorial on them.

I think so! If I’m following what you need, basically you need to print a number of dashes at the end of the hand that is equal to the max number of cards minus the number the player actually has. You can do that with a simple loop at the end of the phrase for saying the abbreviated description of the player’s hand:

To say abbreviated description of the player's hand:
	repeat with flop running through cards held by the player:
		say "[rank of flop as an abbreviated card name] [abbreviation of suit of flop] ";
	repeat with index running from 1 to (focus of the player minus number of cards held by the player):
		say "-- ".

When the player’s hand is full, the last loop runs from 1 to 0, which means it doesn’t run at all! As we want.

A couple other notes about the code:

  1. I couldn’t ever get out of combat even after defeating the spider–I had to add a “now the player is safe” rule to that.
  2. Possibly because of that tweak, it is now possible to redraw even when not in combat. Which then leads to a bad thing when you have a spider card in your hand, if you want to hit the spider the game asks you which you mean, and there’s no way to specify the spider without getting the same question again.
  3. As Daniel said, we really don’t need to change the command prompt every turn–just change it at the beginning of the combat scene.
  4. But it looks kind of weird to me to have the whole hand print for a disambiguation question! I think this could be handled by changing the prompt in a “Before asking which do you mean” rule and then changing it back (during an appropriate scene) in an “After asking which do you mean” rule.
  5. Also I think at the moment we don’t have the rule that makes the focus drop by one when you redraw! (I do think that it’d be good to explicitly tell the player that this decreased their focus.)

This whole concept seems neat! I like your idea of avoiding the tutorial.

Can we get cards played to be replaced with a “-” too? That gets players to make the connection between the - and an empty spot in their hand when they first play a card, that way when they first redraw they know they have an empty spot at the end.

On the notes:

  1. In test encounter, you have to attack the spider three times. After defeating it three times, it spawns the tricky spider. Only when the tricky spider is beaten does the encounter end and things go back to normal. This isn’t obvious at all from the “attack the spider” instruction at the beginning, so an actual finished story definitely needs to be more clear with objectives, but in this case I just needed to be able to test my ability to string multiple engagements together into one encounter. It’s intentionally true that you can move around during an encounter so long as you aren’t engaged, and that your hand doesn’t refresh until you’re completely safe, even if you’re not being attacked right this second. In the actual spider-kicking segment of the story I’m working on, individual spiders are pushovers but you can burn through a redraw or two if you fight each one individually. You can save yourself a bit of trouble by turning the heat on to the pipes and boiling all the spiders who haven’t exited into the cellar, but it’s also possible to brute force your way through if you want. Since it’s the introduction of the whole system, I wanted to make it forgiving enough that players should have enough raw power to be able to blunder their way through.

  2. This bug was in the original version, too! It’s fixed now. Redrawing now checks to make sure you’re engaged first. I’ve also changed all the enemy spiders in the actual story to giant spiders to make it easy to distinguish them from the suit, plus, the spiders in the story are aggressive and will engage the player automatically.

  3. I’ve moved the command prompt change to the “when Encounter begins” rules. While I was there, I added something that keeps track of the current work of the player, to help make it clear that playing a small card does have some effect even when it doesn’t end the engagement immediately.

  4. Generally speaking, I want the hand printed whenever the player is engaged in an encounter, regardless of whether they need to play a card right now. The command prompt got changed from “pick a card” to “Your hand:” to help make this clear. If players are confused when a hand of cards appears in the prompt for the very first time, the “retreat, redraw, or play a card” statement should orient them.

  5. This is why “carry out redrawing” and “when Encounter begins” have nearly identical code repeated in different places in my original code. Redrawing decreases the focus of the player by one, but beginning an encounter does not. That’s an easy fix to implement and it wasn’t really the point of your example code, so I didn’t bother nitpicking it earlier.