Mastering Variables and Logic in InkGameScript

Programming Guide

Introduction

Variables and logic form the backbone of dynamic storytelling in InkGameScript. They transform static narratives into responsive, personalized experiences that adapt to player choices and actions. Whether you're tracking player inventory, managing relationship scores, or creating complex branching scenarios, understanding how to effectively use variables and logic is essential for crafting engaging interactive fiction.

This comprehensive guide will take you from basic variable declaration to advanced logical operations, teaching you how to create stories that remember, react, and evolve. You'll learn not just the syntax, but also best practices for organizing your story's state, debugging common issues, and optimizing performance. By the end, you'll have mastery over InkGameScript's powerful programming features.

Understanding Variables in InkGameScript

Variables in InkGameScript store information that persists throughout your story. They can hold numbers, text, boolean values, or even complex data structures. Let's explore each type and how to use them effectively.

Variable Declaration and Types

InkGameScript uses dynamic typing, but understanding the different types helps you use them correctly:

// Global variables - accessible throughout the story
VAR player_name = "Unknown"
VAR player_health = 100
VAR has_key = false
VAR current_chapter = 1
VAR inventory_items = 0

// Constants - values that never change
CONST MAX_HEALTH = 100
CONST STARTING_GOLD = 50
CONST GAME_VERSION = "1.2.0"

=== start ===
What's your name?
* [John] 
    ~ player_name = "John"
* [Sarah] 
    ~ player_name = "Sarah"
* [Enter custom name]
    ~ player_name = "{~Alex|Sam|Jordan|Casey}"
    
- Welcome, {player_name}!
Your journey begins with {STARTING_GOLD} gold pieces.
-> continue_adventure

Variable Scope

Understanding scope is crucial for managing variables efficiently:

// Global scope - defined at the top level
VAR global_score = 0

=== function calculate_damage(base_damage, multiplier) ===
// Function parameters are local to the function
~ temp total = base_damage * multiplier
~ return total

=== combat_scene ===
// Temporary variables - local to the current knot
~ temp enemy_health = 50
~ temp combat_rounds = 0

The enemy appears with {enemy_health} health.

* [Attack]
    ~ temp damage = calculate_damage(10, 1.5)
    ~ enemy_health -= damage
    ~ combat_rounds += 1
    
    You deal {damage} damage!
    {enemy_health <= 0:
        The enemy is defeated in {combat_rounds} rounds!
        ~ global_score += 100
    - else:
        The enemy has {enemy_health} health remaining.
        -> combat_scene
    }

Working with Text Variables

String manipulation in InkGameScript offers powerful narrative possibilities:

VAR player_title = ""
VAR player_reputation = "unknown"

=== earn_title ===
{player_reputation:
    - "heroic":
        ~ player_title = "the Brave"
    - "cunning":
        ~ player_title = "the Clever"
    - "wealthy":
        ~ player_title = "the Merchant"
    - else:
        ~ player_title = "the Wanderer"
}

People now know you as {player_name} {player_title}.

// String concatenation
~ temp full_name = player_name + " " + player_title

// Using variables in text
The bard sings: "Have you heard of {full_name}? They say 
{player_reputation == "heroic": they slayed a dragon!}
{player_reputation == "cunning": they outwitted the king!}
{player_reputation == "wealthy": they own half the kingdom!}
{player_reputation == "unknown": their story is yet to be written.}"

Mathematical Operations

InkGameScript supports comprehensive mathematical operations for game mechanics:

Basic Arithmetic

VAR player_level = 1
VAR experience = 0
VAR strength = 10
VAR defense = 5

=== gain_experience(amount) ===
~ experience += amount
// Check for level up
{experience >= player_level * 100:
    ~ player_level += 1
    ~ strength += 2
    ~ defense += 1
    LEVEL UP! You are now level {player_level}!
    Your strength increased to {strength}.
    Your defense increased to {defense}.
}

=== calculate_damage ===
~ temp base_damage = strength * 2
~ temp critical_chance = RANDOM(1, 100)
~ temp is_critical = critical_chance <= 10

{is_critical:
    ~ base_damage *= 2
    CRITICAL HIT!
}

~ return base_damage

// Using modulo for cycles
~ temp day_of_week = (current_day % 7) + 1
Today is {day_of_week == 1: Monday}{day_of_week == 2: Tuesday}...

Advanced Calculations

// Percentage calculations
VAR max_mana = 50
VAR current_mana = 30

=== show_mana_bar ===
~ temp mana_percentage = (current_mana * 100) / max_mana
Mana: {current_mana}/{max_mana} ({mana_percentage}%)

// Clamping values
=== heal_player(amount) ===
~ player_health += amount
~ player_health = MIN(player_health, MAX_HEALTH)
Health restored! Current health: {player_health}/{MAX_HEALTH}

// Complex formulas
=== calculate_shop_price(base_price) ===
~ temp charisma_modifier = 1.0 - (player_charisma * 0.02)
~ temp reputation_bonus = 0
{player_reputation == "trusted": 
    ~ reputation_bonus = 0.1
}
~ temp final_price = base_price * charisma_modifier * (1 - reputation_bonus)
~ final_price = MAX(final_price, 1) // Minimum price of 1
~ return INT(final_price)

Conditional Logic

Conditional statements allow your story to respond dynamically to game state:

Basic Conditionals

=== tavern_entrance ===
{player_level < 5:
    The bouncer looks at you dismissively. "Come back when you're stronger, kid."
    -> town_square
}

You enter the tavern.

{has_key:
    * [Go to the secret room] -> secret_room
}

{player_gold >= 10:
    * [Buy a drink (10 gold)] 
        ~ player_gold -= 10
        -> drink_scene
- else:
    The bartender eyes your empty purse. "No money, no service."
}

* [Talk to the mysterious stranger]
    -> mysterious_stranger
    
* [Leave] -> town_square

Complex Conditional Patterns

// Multi-condition checks
=== evaluate_quest_success ===
~ temp success = false

{
    - player_health > 50 && has_holy_sword && defeated_demon:
        ~ success = true
        You completed the quest with flying colors!
        ~ player_reputation = "legendary"
        
    - player_health > 0 && (has_holy_sword || has_magic_shield):
        ~ success = true
        You managed to complete the quest, though barely.
        ~ player_reputation = "heroic"
        
    - player_health > 0:
        You survived, but failed to complete the quest.
        ~ player_reputation = "struggling"
        
    - else:
        Quest failed. You have perished.
        ~ player_reputation = "deceased"
}

// Nested conditions with fallbacks
=== determine_npc_reaction ===
{
    - relationship_score >= 80:
        "{player_name}! My dearest friend!"
        {has_gift:
            "And you brought a gift? How thoughtful!"
            ~ relationship_score += 5
        }
        
    - relationship_score >= 50:
        "Oh, hello {player_name}. Good to see you."
        {completed_npc_quest:
            "Thank you again for your help."
        - else:
            "Have you reconsidered helping me?"
        }
        
    - relationship_score >= 20:
        "What do you want?"
        
    - else:
        "Get away from me!"
        {player_reputation == "villain":
            The guard is alerted to your presence!
            -> guard_encounter
        }
}

Lists: InkGameScript's Power Feature

Lists are one of InkGameScript's most powerful features, providing enum-like functionality with set operations:

Basic List Usage

// Define lists
LIST DaysOfWeek = Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
LIST Inventory = (torch), (rope), (sword), (shield), (potion)
LIST CharacterTraits = brave, cunning, compassionate, ruthless

// Variable to track current state
VAR current_day = Monday
VAR player_items = torch + rope
VAR player_traits = ()

=== start_adventure ===
You begin your journey on a {current_day}.
You're carrying: {LIST_ALL(player_items)}

* [Take the sword]
    ~ player_items += sword
    You pick up the sword.
    
* [Take the shield]
    ~ player_items += shield
    You pick up the shield.

- -> check_inventory

=== check_inventory ===
{LIST_COUNT(player_items) == 0:
    You have no items!
- else:
    You are carrying {LIST_COUNT(player_items)} items: {LIST_ALL(player_items)}
}

{player_items ? sword && player_items ? shield:
    You're well-equipped for combat!
}

Advanced List Operations

LIST Elements = fire, water, earth, air
LIST Spells = (fireball), (ice_shard), (earthquake), (whirlwind), (healing_light)
LIST SpellElements = (fireball, fire), (ice_shard, water), (earthquake, earth), (whirlwind, air)

VAR known_spells = ()
VAR mastered_elements = ()

=== learn_spell(spell) ===
{LIST_ALL(SpellElements) ? (spell):
    ~ temp element = LIST_VALUE(LIST_ALL(SpellElements) ^ (spell))
    ~ known_spells += spell
    
    You learned {spell}!
    
    {not (mastered_elements ? element):
        ~ mastered_elements += element
        You've discovered the {element} element!
    }
- else:
    ERROR: Unknown spell
}

=== cast_spell ===
{LIST_COUNT(known_spells) == 0:
    You don't know any spells.
    -> END
}

Which spell will you cast?
{fireball * known_spells:
    * [Fireball] -> cast_fireball
}
{ice_shard * known_spells:
    * [Ice Shard] -> cast_ice_shard
}
{earthquake * known_spells:
    * [Earthquake] -> cast_earthquake
}

// Checking combinations
=== elemental_mastery ===
~ temp total_elements = LIST_COUNT(mastered_elements)
{
    - total_elements == 4:
        You have mastered all four elements! You are the Avatar!
    - total_elements >= 2:
        You have mastered {LIST_ALL(mastered_elements)}.
        {mastered_elements ? fire && mastered_elements ? water:
            Fire and water together? You've learned balance.
        }
    - else:
        You still have much to learn.
}

Functions: Reusable Logic

Functions help you organize code and avoid repetition:

Creating and Using Functions

=== function min(a, b) ===
{a < b:
    ~ return a
- else:
    ~ return b
}

=== function max(a, b) ===
{a > b:
    ~ return a
- else:
    ~ return b
}

=== function clamp(value, minimum, maximum) ===
~ temp clamped = min(max(value, minimum), maximum)
~ return clamped

// Void functions (no return value)
=== function apply_damage(amount) ===
~ player_health -= amount
~ player_health = max(player_health, 0)
{player_health == 0:
    >> GAME OVER <<
}

// Functions with side effects
=== function give_reward(gold, exp, item) ===
~ player_gold += gold
~ experience += exp
{item != "":
    ~ inventory_items += 1
    You received {item}!
}
You gained {gold} gold and {exp} experience!

=== complete_quest ===
Quest Complete!
~ give_reward(100, 50, "Magic Ring")
-> town_square

Functions with Complex Logic

LIST Relationships = hostile, unfriendly, neutral, friendly, allied
VAR npc_relations = (John, friendly), (Mary, neutral), (Guard, hostile)

=== function get_relationship(npc_name) ===
{LIST_ALL(npc_relations) ? (npc_name):
    ~ temp relation = LIST_VALUE(LIST_ALL(npc_relations) ^ (npc_name))
    ~ return relation
- else:
    ~ return neutral
}

=== function modify_relationship(npc_name, change) ===
~ temp current = get_relationship(npc_name)
~ temp all_relations = LIST_ALL(Relationships)

{
    - change > 0 && current != allied:
        // Improve relationship
        ~ temp index = LIST_VALUE(current)
        ~ temp new_relation = LIST_MIN(all_relations ^ (index + 1))
        ~ npc_relations -= (npc_name, current)
        ~ npc_relations += (npc_name, new_relation)
        Your relationship with {npc_name} improved to {new_relation}!
        
    - change < 0 && current != hostile:
        // Worsen relationship
        ~ temp index = LIST_VALUE(current)
        ~ temp new_relation = LIST_MIN(all_relations ^ (index - 1))
        ~ npc_relations -= (npc_name, current)
        ~ npc_relations += (npc_name, new_relation)
        Your relationship with {npc_name} worsened to {new_relation}!
}

State Machines and Complex Logic

For complex game systems, you can implement state machines using InkGameScript:

Quest State Machine

LIST QuestStates = not_started, active, completed, failed
VAR main_quest_state = not_started
VAR quest_progress = 0
CONST QUEST_STEPS = 5

=== function advance_quest() ===
{main_quest_state == active:
    ~ quest_progress += 1
    Quest progress: {quest_progress}/{QUEST_STEPS}
    
    {quest_progress >= QUEST_STEPS:
        ~ main_quest_state = completed
        Quest completed!
        ~ give_reward(500, 200, "Legendary Sword")
    }
}

=== function fail_quest() ===
{main_quest_state == active:
    ~ main_quest_state = failed
    ~ quest_progress = 0
    Quest failed. You'll need to start over.
}

=== quest_giver ===
{main_quest_state:
    - not_started:
        "Brave adventurer, will you help us?"
        * [Accept quest]
            ~ main_quest_state = active
            "Excellent! You must complete {QUEST_STEPS} tasks."
            -> start_quest
        * [Decline]
            "That's unfortunate. Come back if you change your mind."
            
    - active:
        "You've completed {quest_progress} of {QUEST_STEPS} tasks."
        {quest_progress < QUEST_STEPS:
            "Keep going!"
        - else:
            "You've done it all! Here's your reward!"
            ~ advance_quest()
        }
        
    - completed:
        "Thank you again for your help!"
        
    - failed:
        "I'm disappointed, but perhaps you'd like to try again?"
        * [Try again]
            ~ main_quest_state = active
            ~ quest_progress = 0
            "I believe in second chances."
        * [No thanks]
            "I understand."
}

Combat System Example

LIST CombatStates = player_turn, enemy_turn, victory, defeat
VAR combat_state = player_turn
VAR enemy_health = 100
VAR enemy_max_health = 100
VAR enemy_damage = 15

=== function start_combat(enemy_name, health, damage) ===
~ enemy_health = health
~ enemy_max_health = health
~ enemy_damage = damage
~ combat_state = player_turn
Battle begins with {enemy_name}!
-> combat_loop

=== combat_loop ===
{combat_state == victory || combat_state == defeat:
    -> combat_end
}

Player: {player_health}/{MAX_HEALTH} HP
Enemy: {enemy_health}/{enemy_max_health} HP

{combat_state:
    - player_turn:
        -> player_combat_turn
    - enemy_turn:
        -> enemy_combat_turn
}

=== player_combat_turn ===
Your turn!
* [Attack]
    ~ temp damage = calculate_damage()
    ~ enemy_health -= damage
    You deal {damage} damage!
    
    {enemy_health <= 0:
        ~ combat_state = victory
    - else:
        ~ combat_state = enemy_turn
    }
    -> combat_loop
    
* [Defend]
    ~ temp defense_bonus = 10
    You raise your guard! (+{defense_bonus} defense this turn)
    ~ combat_state = enemy_turn
    -> combat_loop
    
* [{player_potions > 0} Use Potion]
    ~ player_potions -= 1
    ~ temp heal_amount = 50
    ~ player_health = min(player_health + heal_amount, MAX_HEALTH)
    You restore {heal_amount} health!
    ~ combat_state = enemy_turn
    -> combat_loop

=== enemy_combat_turn ===
Enemy attacks!
~ temp damage = enemy_damage - defense
~ damage = max(damage, 1) // Minimum 1 damage
~ player_health -= damage

The enemy deals {damage} damage!

{player_health <= 0:
    ~ combat_state = defeat
- else:
    ~ combat_state = player_turn
}
-> combat_loop

=== combat_end ===
{combat_state:
    - victory:
        Victory! You defeated the enemy!
        ~ experience += 50
        ~ player_gold += 30
        -> continue_adventure
        
    - defeat:
        You have been defeated...
        -> game_over
}

Debugging Variables and Logic

Effective debugging techniques for complex variable systems:

Debug Output Functions

// Create debug functions to track state
=== function DEBUG_show_stats() ===
>>> DEBUG INFO <<<
Player: {player_name} (Level {player_level})
Health: {player_health}/{MAX_HEALTH}
Gold: {player_gold}
Items: {LIST_COUNT(player_items)} - {LIST_ALL(player_items)}
Quest State: {main_quest_state} ({quest_progress}/{QUEST_STEPS})
>>> END DEBUG <<<

// Conditional debugging
CONST DEBUG_MODE = true

=== function debug_log(message) ===
{DEBUG_MODE:
    [DEBUG: {message}]
}

// Usage in story
=== some_scene ===
~ debug_log("Entering some_scene")
~ temp old_health = player_health
Something happens here...
~ debug_log("Health changed from {old_health} to {player_health}")

Variable Watchers

// Track variable changes
VAR prev_health = 100
VAR health_changes = ""

=== function track_health() ===
{player_health != prev_health:
    ~ temp change = player_health - prev_health
    ~ health_changes = health_changes + "Health {change > 0: +}{change} "
    ~ prev_health = player_health
}

// Assert functions for testing
=== function assert(condition, message) ===
{not condition:
    ERROR: Assertion failed - {message}
    ~ DEBUG_show_stats()
}

=== test_damage_calculation ===
~ temp test_health = player_health
~ apply_damage(10)
~ assert(player_health == test_health - 10, "Damage calculation wrong")

Best Practices for Variables and Logic

Follow these guidelines for clean, maintainable code:

1. Naming Conventions

// Use clear, descriptive names
VAR player_current_health = 100  // Good
VAR h = 100                      // Bad

// Use prefixes for categories
VAR stat_strength = 10
VAR stat_intelligence = 12
VAR inv_sword_count = 1
VAR quest_main_progress = 0

// Constants in UPPER_CASE
CONST MAX_LEVEL = 50
CONST SAVE_SLOTS = 3

2. Organize Related Variables

// Group related variables together
// === Player Stats ===
VAR player_level = 1
VAR player_health = 100
VAR player_mana = 50
VAR player_gold = 0

// === Quest Variables ===
VAR quest_main_state = not_started
VAR quest_main_progress = 0
VAR quest_side_baker = false
VAR quest_side_blacksmith = false

// === Combat Variables ===
VAR combat_rounds = 0
VAR combat_enemies_defeated = 0
VAR combat_damage_dealt = 0

3. Avoid Magic Numbers

// Bad: Magic numbers
{player_health < 20:
    You're badly wounded!
}

// Good: Named constants
CONST LOW_HEALTH_THRESHOLD = 20
{player_health < LOW_HEALTH_THRESHOLD:
    You're badly wounded!
}

// For calculations
CONST CRIT_CHANCE_PERCENT = 10
CONST CRIT_DAMAGE_MULTIPLIER = 2.0
~ temp is_critical = RANDOM(1, 100) <= CRIT_CHANCE_PERCENT
{is_critical:
    ~ damage *= CRIT_DAMAGE_MULTIPLIER
}

4. Encapsulate Complex Logic

// Instead of repeating complex conditions
{player_level >= 10 && player_gold >= 1000 && completed_training:
    // Can access special shop
}

// Create a function
=== function can_access_special_shop() ===
~ return player_level >= 10 && player_gold >= 1000 && completed_training

// Use it cleanly
{can_access_special_shop():
    * [Enter Special Shop] -> special_shop
}

Performance Optimization

Tips for keeping your variable-heavy stories performant:

Minimize Global Variables

// Instead of many globals for temporary combat
VAR combat_enemy1_health = 0
VAR combat_enemy2_health = 0
VAR combat_enemy3_health = 0

// Use a list or local variables
=== combat_encounter ===
~ temp enemy_count = 3
~ temp enemy1_health = 50
~ temp enemy2_health = 50
~ temp enemy3_health = 50

// Or use a list for dynamic enemy counts
LIST Enemies = enemy1, enemy2, enemy3
VAR enemy_healths = (enemy1, 50), (enemy2, 50), (enemy3, 50)

Clean Up Unused Variables

=== function cleanup_chapter_variables() ===
// Reset chapter-specific variables
~ temp_chapter_npc_met = false
~ temp_chapter_puzzle_solved = false
~ temp_chapter_items = ()

=== chapter_transition ===
Chapter {current_chapter} complete!
~ cleanup_chapter_variables()
~ current_chapter += 1
-> start_new_chapter

Conclusion

Variables and logic are the heart of dynamic storytelling in InkGameScript. From simple counters to complex state machines, they enable you to create stories that respond uniquely to each player's journey. The key to mastery is understanding not just how to use these features, but when and why to use them.

Remember that the best interactive fiction balances technical complexity with narrative flow. Use variables and logic to enhance your storytelling, not overshadow it. Start simple, test often, and gradually build complexity as you become more comfortable with the system.

With these tools at your disposal, you're ready to create truly responsive and engaging interactive narratives. Happy coding, and may your stories come alive with endless possibilities!

Variables Logic Programming InkGameScript Tutorial