Card Crawler on Android


Categories: Projects Tags: Ren'Py


While I was exploring traditional deck games I found and loved Jean-Baptiste Vincent’s Card Crawler. Unlike the other card games I’ve worked on to this point, Card Crawler has a map created from a grid of 25 cards. When I play at home it just barely fits on my game table, and I was concerned about ’tablespace’ on the app as well. The other difference is Aces have a considered value of 1 rather than 14.


The Deck we generate is also a little different. Hearts 1-10 are reserved for HP tracking. The player starts with Spades, Clubs, and Diamonds 1-6:

player_deck = [Card(value,suit) for value in range(1,7) for suit in suits[1:]]

The remaining 25 cards are the rest of the deck and the Jokers.

remaining_values = [Card(value,suit) for value in range(7,11) for suit in suits[1:]]
royals = [Card(value,suit) for value in range(11,14) for suit in suits]
joker = Card(0,joker_suit)

My grid math was rusty so I took a little time to sort out how to use a single-level array for my grid.

if not n%5, can move left if n%5 < (5-1), can move right if n >= 5, can move up if n < 20, can move down

Now we have a functional grid. The player can only “move” to an orthogonal card, so having them click the gird itself eliminates the need for a DPAD or some other directional control. Our functions will only need to consider the move and not if it’s legal.

I thought about how to handle the grid data and if I should expand the card class to store additional data beyond suit/value. We need to know if a card on the grid has been flipped or not. Additionally, if a monster is not looted before the hero leaves the room it cannot be looted again, but treasure can still be looted after the fact. So that’s two statuses: visible and lootable.


I decided to work off the base class. When we generate cards now we mark the starting deck as visible and unlootable, and do the reverse for the cards in the grid.

    class Card(object):
        def __init__(self, value, suit):
            self.value = value
            self.suit = suit
class CrawlerCard(Card):
    def __init__(self, value, suit, visible, loot):
        self.visible = visible
        self.loot = loot

And what I learned from earlier games is the lifting needs to be done in Python functions, not screen language. So we will just call a single function, Move, and go from there. Voila, we now have fog of war–the map is revealed as we explore.

Next we need to maintain a hand, and then the actions available depend on the room the player is currently in. Since this is a deckbuilder with no round limit (the end condition is 0 hp), we can theoretically deal infinitely, so this game is most like Card Capture in its hand-management mechanics. We automatically fill the hand to the limit each time, and if the deck runs out, we shuffle the discards, add them, and continue dealing.

Now, something to keep in mind with this game is cards in hand are only relevant in terms of value (action points) and if it’s a Diamond, which will be worth a bonus at the end of the game. We will change that later, but for now all we’re really looking at is the value for points to spend.

We add a discard function, and for testing we go ahead and add a way to discard the hand.

Next, the most basic action is looting treasure. We add a basic swap function, we’ll refine the rules later.

        # loot mechanic, swap card in hand for current location card
        def swap(self,card):
            gained_card = self.dungeon[self.hero_index]
            gained_card.loot = False
            self.dungeon[self.hero_index] = card

Now we have a basic card economy set up so we will update the move function to require a discard as the cost.

Now it’s time to address monsters. They have 3 states.

So for now, having a loot field is adequate to identify them.

There are some other monsters, though. Kings of any suit are a type of MiniBoss. And sometimes a Spade or Club is not a monster, it’s a companion or event.


Still thinking about the most intuitive way to select cards. Sometimes you only select one but some require multiples. May go to Card Capture’s method of select action > select card(s) < Confirm to avoid errors.

I think I will have the map direction still be selectable from the grid, and everything else is a “room action” and you select from the list.


After some thought I decided to put this one aside for now. I think the map and vibe add just enough complexity it doens’t quite “fit in” with the other card games, which I am going to combine into a single app for personal use. This is how far I got:

basic map movement fog of war