[Game]Duel Paws

duel-paws-screenshot-x2

Duel Paws is a 2-player deckbuilding game in which you are the leader of a bunch of cats. You must ensure your opponent’s cats run out of patience before yours do.

This started as a demake of a game called “To Arms!!”, but it has diverged quite a bit and become a very different game. There are still some fundamental similarities.

There is a 2-player pass-and play mode and a single-player mode versus AI. Instructions on how to play are included in the game.

This was made for the Cute Little Demake game jam.

Version 1.2.0 now available! Source and pop can be downloaded on itch.io!

Download right here:

Old content for version 1.0.0

duel-paws-v1.0.0.pop.1

Source code

Duel Paws was written using the pyinsky online python editor. If you want to build this yourself, you can download the project zip:

Duel Paws v1.0.0.zip (27.9 KB)

Also, you can browse the source files here:

main.py
'''
    Duel Paws, a 2-player deckbuilding game.
    Copyright (C) 2021 wuuff

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''
# main.py
import upygame as pygame
from cards import *

screen = pygame.display.set_mode()

pygame.display.set_palette_16bit([
0x0000, 0x194a, 0x790a, 0x0429, 0xa286, 0x5aa9, 0xbe18, 0xff7c, 0xe0c9, 0xf503, 0xf745, 0x5dc9, 0x4d3a, 0x7392, 0xebb4, 0xf654
])

cardSurf = pygame.surface.Surface(16, 16, cardPixels)
cursorSurf = pygame.surface.Surface(16, 16, cursorPixels)
for card in friendlyCards:
    card.append(pygame.surface.Surface(8, 8, card[PIXELS]))
for card in hostileCards:
    card.append(pygame.surface.Surface(8, 8, card[PIXELS]))

cx = 0
cy = 0
cxmax = 5

ai_enabled = False
    
def titleScreen():
    global cy
    global ai_enabled
    while True:
        random(2) # Call random repeatedly so each game is random
        eventtype = pygame.event.poll()
        if eventtype != pygame.NOEVENT:
            if eventtype.type == pygame.KEYDOWN:
                if eventtype.key == pygame.K_DOWN or eventtype.key == pygame.K_UP:
                    cy = (cy+1)%2
                if eventtype.key == pygame.BUT_A:
                    if cy == 0:
                        ai_enabled = True
                    else:
                        ai_enabled = False
                    return
                if eventtype.key == pygame.BUT_C:
                    pygame.display.flip()
                    tutorialScreen()
        pygame.draw.text(38,10,"Duel Paws",10)
        pygame.draw.text(86,0,"v1.0.0",5)
        if cy == 0:
            pygame.draw.text(42,30,">VS AI<",10)
            pygame.draw.text(46,40,"VS 2P",10)
        else:
            pygame.draw.text(46,30,"VS AI",10)
            pygame.draw.text(42,40,">VS 2P<",10)
        pygame.draw.text(18,80,"Press C for tutorial",11)
        pygame.display.flip()
    
def winScreen(which):
    while True:
        eventtype = pygame.event.poll()
        if eventtype != pygame.NOEVENT:
            # A hack to make sure title screen isn't skipped: check on button up here
            if eventtype.type == pygame.KEYUP:
                if eventtype.key == pygame.BUT_A:
                    titleScreen()
                    return
        pygame.draw.text(32,40,"Player %d Wins!"%(which+1),10)
        pygame.display.flip()
        
def tutorialScreen():
    global cx
    global cy
    textind = 0
    initGame()
    while True:
        eventtype = pygame.event.poll()
        if eventtype != pygame.NOEVENT:
            if eventtype.type == pygame.KEYDOWN:
                if eventtype.key == pygame.BUT_A:
                    textind += 1
                    if textind >= len(helpText):
                        cy = 0
                        return
                if eventtype.key == pygame.BUT_B:
                    if textind == 0:
                        cy = 0
                        return
                    textind -= 1
        renderGame(False)
        cy = helpY[textind]
        pygame.draw.text(0+1,64,helpText[textind],6)
        pygame.display.flip()
        

enemyHand = []
playerHand = None
playerDiscard = None
playerDeck = None
playerHands = [[],[]]
playerDiscards = [[],[]]
playerDecks = [[],[]]
shopCards = []

patience = 100
patiences = [100,100]
treats = 0
pounces = 0
adopts = 0

rstate = 45

#xorshift random number generator
def random(val):
    global rstate
    rstate ^= rstate << 7
    rstate %= 65536
    rstate ^= rstate >> 9
    rstate %= 65536
    rstate ^= rstate << 8
    rstate %= 65536
    return rstate % val

currentPlayer = 0

def swapPlayers():
    global playerHand
    global playerDiscard
    global playerDeck
    global currentPlayer
    global patience
    patiences[currentPlayer] = patience
    currentPlayer = (currentPlayer+1)%2
    playerHand = playerHands[currentPlayer]
    playerDiscard = playerDiscards[currentPlayer]
    playerDeck = playerDecks[currentPlayer]
    patience = patiences[currentPlayer]

def setCXMax(index):
    global cxmax
    global cx
    if index == 0:
        cxmax = len(enemyHand)
    elif index == 1:
        cxmax = len(shopCards)
    elif index == 2:
        cxmax = len(playerHand)
    elif index == 3:
        cxmax = len(playerDiscard)
    if cxmax == 0:
        cxmax = 1
    if cx >= cxmax:
        cx = cxmax-1

def initCards(hands,discards,decks):
    # Clear all cards
    global shopCards
    global enemyHand
    shopCards = []
    enemyHand = []
    hands[0] = []
    hands[1] = []
    discards[0] = []
    discards[1] = []
    decks[0] = []
    decks[1] = []
    shopCards.append(RANDOMIZE)
    enemyHand.append(RANDOMIZE2)
    for i in range(5):
        enemyHand.append(len(friendlyCards)+random(len(hostileCards)-1))
        shopCards.append(random(len(friendlyCards)-1))
        # Sleepy cat
        discards[0].append(0)
        discards[1].append(0)
    for i in range(5):
        # Scruffy cat
        discards[0].append(1)
        discards[1].append(1)
        
def swap(l,a,b):
    temp = l[a]
    l[a] = l[b]
    l[b] = temp
        
def shuffle(deck):
    for i in range(len(deck)):
        swap(deck,i, random(len(deck)))
        
def getCard(index):
    if index >= len(friendlyCards):
        return hostileCards[index-len(friendlyCards)]
    return friendlyCards[index]
    
def tryDestroy(cards,index):
    global treats
    global pounces
    if len(cards) == 0:
        return
    card = getCard(cards[index])
    if card[COST] <= pounces:
        pounces -= card[COST]
        if cards[index] == RANDOMIZE2:
            # Randomize enemy pool
            while len(cards) > 1:
                cards.pop()
            for i in range(5):
                cards.append(len(friendlyCards)+random(len(hostileCards)-1))
        elif cards[index] == TASTY_FISH:
            # Instantly add treats, don't add card to deck
            treats += card[TREATS]
            cards.pop(index)
            cards.append(len(friendlyCards)+random(len(hostileCards)-1))
        else:
            cind = cards.pop(index)
            cards.append(len(friendlyCards)+random(len(hostileCards)-1))
            # Send to other player's discard
            playerDiscards[(currentPlayer+1)%2].append(cind)

def tryBuy(cards,index,discard):
    global treats
    global pounces
    if len(cards) == 0:
        return
    card = getCard(cards[index])
    if card[COST] <= treats:
        treats -= card[COST]
        if cards[index] == RANDOMIZE:
            # Randomize shop
            while len(cards) > 1:
                cards.pop()
            for i in range(5):
                shopCards.append(random(len(friendlyCards)-1))
        elif cards[index] == POWER_NAP:
            # Instantly add pounces, don't add card to deck
            pounces += card[POUNCES]
            cards.pop(index)
            cards.append(random(len(friendlyCards)-1))
        else:
            discard.append(cards[index])
            cards.pop(index)
            cards.append(random(len(friendlyCards)-1))
        
def tryAdopt(cards,index):
    global adopts
    if len(cards) == 0:
        return
    card = getCard(cards[index])
    if adopts > 0:
        adopts -= 1
        cards.pop(index)
    
def enemyAction(hand):
    global patience
    patience -= 2
    #for index in hand:
    #    card = getCard(index)
    #    patience += card[PATIENCE]
        
def endTurn():
    global cx
    global cy
    swapPlayers()
    enemyAction(enemyHand)
    drawCards(playerHand,playerDiscard,playerDeck)
    if patience <= 0:
        if currentPlayer == 0:
            winScreen(1)
        else:
            winScreen(0)
        initGame()
    cy = 2
    cx = 0
    setCXMax(cy)
    
def tryAction():
    if cy == 0:
        tryDestroy(enemyHand,cx)
    elif cy == 1:
        tryBuy(shopCards,cx,playerDiscard)
    elif cy == 3:
        tryAdopt(playerDiscard,cx)
    setCXMax(cy)
        
def takeInput():
    global cx
    global cy
    eventtype = pygame.event.poll()
    if eventtype != pygame.NOEVENT:
        if eventtype.type== pygame.KEYDOWN:
            if eventtype.key == pygame.K_RIGHT:
                cx += 1
                cx %= cxmax
            if eventtype.key == pygame.K_LEFT:
                cx -= 1
                if cx < 0:
                    cx = cxmax-1
            if eventtype.key == pygame.K_UP:
                cy -= 1
                if cy < 0:
                    cy = 3
                setCXMax(cy)
            if eventtype.key == pygame.K_DOWN:
                cy += 1
                cy %= 4
                setCXMax(cy)
            if eventtype.key == pygame.BUT_B:
                endTurn()
            if eventtype.key == pygame.BUT_A:
                tryAction()
    
ai_timer = 0
ai_targetx = -1
ai_targety = -1
ai_showhand = True

def setAITarget():
    global ai_targetx
    global ai_targety
    global ai_showhand
    if ai_showhand:
        ai_targety = 2
        ai_targetx = len(playerHand)-1
        ai_showhand = False
        return
    # Adopt hostile cards and sleepy cats
    if adopts > 0 and len(playerDiscard) > 0:
        for cind in range(len(playerDiscard)):
            card = playerDiscard[cind]
            if card == 0 or card >= len(friendlyCards):
                ai_targety = 3
                ai_targetx = cind
                return
    # Try to buy the most expensive cards it can afford that cost more than 1 (and don't buy power naps)
    # Index of zero is randomizer, which we can ignore
    most_expensive = 0
    most_expensive_ind = 0
    for cind in range(len(shopCards)):
        cardind = shopCards[cind]
        card = getCard(cardind)
        if card[COST] > 1 and treats >= card[COST] and cardind != POWER_NAP:
            if card[COST] > most_expensive:
                most_expensive = card[COST]
                most_expensive_ind = cind
    if most_expensive > 0:
        ai_targety = 1
        ai_targetx = most_expensive_ind
        return
    # Try to buy the most expensive cards it can afford that cost more than 1
    # Index of zero is randomizer, which we can ignore
    most_expensive = 0
    most_expensive_ind = 0
    for cind in range(len(enemyHand)):
        cardind = enemyHand[cind]
        card = getCard(cardind)
        if card[COST] > 1 and pounces >= card[COST]:
            if card[COST] > most_expensive:
                most_expensive = card[COST]
                most_expensive_ind = cind
    if most_expensive > 0:
        ai_targety = 0
        ai_targetx = most_expensive_ind
        return
    # Random chance of randomizing cards
    if random(2) == 0:
        if random(2) == 0:
            if pounces > 0:
                ai_targety = 0
                ai_targetx = 0
                return
            elif treats > 0:
                ai_targety = 1
                ai_targetx = 0
                return
        else:
            if treats > 0:
                ai_targety = 1
                ai_targetx = 0
                return
            elif pounces > 0:
                ai_targety = 0
                ai_targetx = 0
                return
            
def runAIStep():
    global cx
    global cy
    global ai_timer
    global ai_targetx
    global ai_targety
    global ai_showhand
    ai_timer += 1
    if ai_timer >= 20:
        ai_timer = 0
        # If no target is set, find a target
        if ai_targetx == -1:
            setAITarget()
            # If after setting it, it's still not set, end turn
            if ai_targetx == -1:
                endTurn()
                ai_targetx = -1
                ai_targety = -1
                ai_showhand = True
                return
        if ai_targety < cy:
            cy -= 1
            setCXMax(cy)
        elif ai_targety > cy:
            cy += 1
            setCXMax(cy)
        elif ai_targetx < cx:
            cx -= 1
        elif ai_targetx > cx:
            cx += 1
        else:
            # On target card
            tryAction()
            ai_targetx = -1
            ai_targety = -1
        
def drawCards(hand,discard,deck):
    global treats
    global pounces
    global adopts
    global patience
    treats = 0
    pounces = 0
    adopts = 0
    while len(hand) > 0:
        discard.append(hand.pop())
    # Hand size may be increased by some cards
    handsize = 5
    while len(hand) < handsize:
        if len(deck) == 0:
            if len(discard) == 0:
                # If we have completely run out of cards, give up and stop
                break
            while len(discard) > 0:
                deck.append(discard.pop())
            shuffle(deck)
        index = deck.pop()
        card = getCard(index)
        handsize += card[EXTRA]
        treats += card[TREATS]
        pounces += card[POUNCES]
        adopts += card[ADOPTS]
        patience += card[PATIENCE]
        hand.append(index)
    # Avoid negative treats and pounces
    if treats < 0:
        treats = 0
    if pounces < 0:
        pounces = 0
        
def renderCards(cards,height):
    if len(cards) > 0:
        screen.blit(cursorSurf, cx*16+1, height)
    for index in range(len(cards)):
        cardindex = cards[index]
        screen.blit(cardSurf, index*16+1, height)
        if cardindex >= len(friendlyCards):
            cardindex -= len(friendlyCards)
            screen.blit(hostileCards[cardindex][SURFACE], 4+index*16+1, height+4)
        else:
            screen.blit(friendlyCards[cardindex][SURFACE], 4+index*16+1, height+4)

def renderInfo(cards,height):
    # Draw which player's turn it is
    if currentPlayer == 0:
        pygame.draw.text(100+1,height+16,'P1',11)
    else:
        if ai_enabled:
            pygame.draw.text(100+1,height+16,'AI',12)
        else:
            pygame.draw.text(100+1,height+16,'P2',12)
    # Guard against drawing details when no card is present
    if len(cards) == 0:
        pygame.draw.text(0+1,height,'No cards here!',6)
        return
    # Draw card details
    index = cards[cx]
    if index >= len(friendlyCards):
        if index == TASTY_FISH:
            pygame.draw.text(64+1,height+8,'When Bought',6)
        index -= len(friendlyCards)
        pygame.draw.text(0+1,height,hostileCards[index][NAME],6)
        pygame.draw.text(77+1,height,'Cost: %dP' % hostileCards[index][COST],6)
        if hostileCards[index][TREATS] < 0:
            pygame.draw.text(0+1,height+8,'%d Treat' % hostileCards[index][TREATS],6)
        elif hostileCards[index][TREATS] > 0:
            pygame.draw.text(0+1,height+8,'+%d Treat' % hostileCards[index][TREATS],6)
        if hostileCards[index][POUNCES] < 0:
            pygame.draw.text(28+1,height+8,'%d Pounce' % hostileCards[index][POUNCES],6)
        if hostileCards[index][POUNCES] > 0:
            pygame.draw.text(28+1,height+8,'+%d Pounce' % hostileCards[index][POUNCES],6)
        if hostileCards[index][PATIENCE] < 0:
            pygame.draw.text(64+1,height+8,'%d Patience' % hostileCards[index][PATIENCE],6)
        pygame.draw.text(0+1,height+16,hostileCards[index][DESC],6)
    else:
        pygame.draw.text(0+1,height,friendlyCards[index][NAME],6)
        pygame.draw.text(77+1,height,'Cost: %dT' % friendlyCards[index][COST],6)
        if friendlyCards[index][TREATS] > 0:
            pygame.draw.text(0+1,height+8,'+%d Treat' % friendlyCards[index][TREATS],6)
        if friendlyCards[index][POUNCES] > 0:
            pygame.draw.text(28+1,height+8,'+%d Pounce' % friendlyCards[index][POUNCES],6)
        # Currently, no concurrent adoptions, extra cards, or patience
        if friendlyCards[index][PATIENCE] > 0:
            pygame.draw.text(64+1,height+8,'+%d Patience' % friendlyCards[index][PATIENCE],6)
        if friendlyCards[index][EXTRA] > 0:
            pygame.draw.text(64+1,height+8,'+%d Cards' % friendlyCards[index][EXTRA],6)
        if friendlyCards[index][ADOPTS] > 0:
            pygame.draw.text(64+1,height+8,'+%d Adoption' % friendlyCards[index][ADOPTS],6)
        if index == POWER_NAP:
            pygame.draw.text(64+1,height+8,'When Bought',6)
        pygame.draw.text(0+1,height+16,friendlyCards[index][DESC],6)
        
def renderGame(showInfo):
    drawHeight = 8
    pygame.draw.text(0+1,0,"Patience: %d" % patience,8)
    pygame.draw.text(60+1,0,"T:%d P:%d A:%d" %(treats,pounces,adopts),10)
    pygame.draw.text(0+1,8,"Enemy Forces",7)
    if cy == 0:
        renderCards(enemyHand,drawHeight+6)
        if showInfo:
            renderInfo(enemyHand,62)
        drawHeight += 22
    else:
        drawHeight += 8
    pygame.draw.text(0+1,drawHeight,"Cat Shoppe",7)
    if cy == 1:
        renderCards(shopCards,drawHeight+6)
        if showInfo:
            renderInfo(shopCards,62)
        drawHeight += 22
    else:
        drawHeight += 8
    pygame.draw.text(0+1,drawHeight,"Your Hand",7)
    if cy == 2:
        renderCards(playerHand,drawHeight+6)
        if showInfo:
            renderInfo(playerHand,62)
        drawHeight += 22
    else:
        drawHeight += 8
    pygame.draw.text(0+1,drawHeight,"Your Discard",7)
    if cy == 3:
        renderCards(playerDiscard,drawHeight+6)
        if showInfo:
            renderInfo(playerDiscard,62)
        drawHeight += 22
    else:
        drawHeight += 8
    pygame.draw.text(0+1,drawHeight,"Info Zone",10)

def initGame():
    global patience
    global currentPlayer
    global cy
    global cx
    patience = 100
    patiences[0] = 100
    patiences[1] = 100
    currentPlayer = 0
    cy = 2
    cx = 0
    initCards(playerHands,playerDiscards,playerDecks)
    swapPlayers()
    drawCards(playerHand,playerDiscard,playerDeck)
    swapPlayers()
    enemyAction(enemyHand)
    drawCards(playerHand,playerDiscard,playerDeck)
    
titleScreen()
initGame()

while True:
    if currentPlayer == 0 or (currentPlayer == 1 and not ai_enabled):
        takeInput()
    else:
        runAIStep()
                    
    #screen.blit(cardSurf, 14, 14)
    #screen.blit(friendlyCards[0][-1], 18, 18)
    
    #print(playerHand)
    renderGame(True)
    pygame.display.flip()
cards.py
'''
    Duel Paws, a 2-player deckbuilding game.
    Copyright (C) 2021 wuuff

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''
# cards.py

helpText = [
    'Duel Paws is a 2-player\ndeckbuilding card game.',
    'Each player is in charge\nof a group of cats, but\nthe cats are impatient.',
    'Your objective is for your\nopponent to run out of\npatience before you do.',
    'Cards play automatically\nand your full hand displays\nat the start of each turn.',
    'You have 3 resources:\nTreats, Pounces, and\nAdoptions.',
    'Treats can be used to buy\ncats at the shoppe. Bought\ncats go in your discard.',
    #'Cards in the "Enemy Forces"\nrow reduce your patience\nat the start of your turn.',
    'Pounces are used to defeat\nenemy forces. Enemy cards go\nin your opponent\'s discard.',
    'All of the effects on enemy\ncards will happen when they\nare played in your hand.',
    'Also, your patience level is\nreduced by 2 at the start\nof your turn.',
    'Adoptions remove cards from\nyour discard. Any card in\nyour discard can be adopted.',
    'Cards that give an effect\nwhen bought are not added\nto your discard.',
    'When your deck runs out of\ncards, your discard is\nshuffled back in your deck.',
    'Your deck is not shown.',
    'Press A to buy, defeat,\nor adopt cards.\nPress B to end your turn.',
    ]
    
helpY = [
    2,
    2,
    2,
    2,
    2,
    1,
    0,
    0,
    0,
    3,
    3,
    3,
    3,
    3,
    ]

cardPixels = b'\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\xff\xff\xff\xff\xff\xff\x00\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x00\xff\xff\xff\xff\xff\xff\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\
'

cursorPixels = b'\
\x07\x70\x00\x00\x00\x00\x07\x70\
\x77\xff\xff\xff\xff\xff\xff\x77\
\x7f\x11\x11\x11\x11\x11\x11\xf7\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x0f\x11\x11\x11\x11\x11\x11\xf0\
\x7f\x11\x11\x11\x11\x11\x11\xf7\
\x77\xff\xff\xff\xff\xff\xff\x77\
\x07\x70\x00\x00\x00\x00\x07\x70\
'

sleepyCatPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x16\x11\x11\x11\
\x61\x61\x61\x61\
\x11\x61\x66\x66\
\x16\x66\x65\x65\
\x16\x66\x66\x66\
\x66\x66\x66\x66\
'

scruffyCatPixels = b'\
\x11\x11\x11\x11\
\x14\x14\x11\x11\
\x45\x45\x41\x11\
\x44\x44\x41\x14\
\x14\x84\x11\x44\
\x14\x44\x44\x41\
\x14\x44\x44\x41\
\x44\x41\x14\x41\
'

cuteCatPixels = b'\
\x11\x11\x11\x11\
\x11\xee\x1e\xe1\
\x11\xee\xee\xe1\
\x11\x5e\x6e\x51\
\x11\x51\x61\x51\
\x11\x66\x66\x61\
\x11\x56\x65\x11\
\x15\x56\x66\x11\
'

partyCatPixels = b'\
\x11\x11\x11\x81\
\x11\x11\x1e\x81\
\x16\x11\x18\x88\
\x61\x61\x8e\x8e\
\x11\x61\x66\x66\
\x16\x66\x65\x65\
\x16\x66\x66\x66\
\x66\x66\x66\x66\
'

chillCatPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x21\x21\x11\x11\
\x22\x21\x11\x11\
\x22\x2d\xdd\xd1\
\xdd\xdd\xdd\xd2\
\x22\x2d\x22\x22\
\x11\x11\x11\x11\
'

socialCatPixels = b'\
\x11\x11\x11\x16\
\x11\x11\x11\x16\
\x91\x11\x11\x81\
\x19\x19\x11\x18\
\x91\x19\x91\x18\
\x99\x99\x91\x81\
\x99\x91\x11\x11\
\x91\x91\x11\x11\
'

coffeeCatPixels = b'\
\x11\x44\x41\x11\
\x11\x11\x14\x14\
\x11\x41\x44\x41\
\x16\x44\x44\x11\
\x14\x44\x11\x11\
\x11\x41\x11\x11\
\x14\x11\x11\x11\
\x11\x11\x11\x11\
'

wharfCatPixels = b'\
\x16\x16\x11\x11\
\x16\x66\x11\x11\
\x16\x66\x16\x11\
\x16\x6d\x11\x61\
\x16\x66\x14\x41\
\x1d\x66\x64\x41\
\x44\x44\x44\x44\
\x11\x11\x14\x41\
'

hunterCatPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x19\x19\x11\
\x11\x17\x97\x13\
\x13\x13\x99\x31\
\x31\x39\x39\x31\
\x11\x3b\x39\x31\
\x11\x13\x33\xb1\
'

demandingCatPixels = b'\
\x11\x11\x11\x11\
\x51\x11\x11\x15\
\x55\x11\x11\x55\
\x55\x51\x16\x55\
\x55\x55\x66\x55\
\x57\x06\x67\x05\
\x50\x06\x60\x05\
\x56\x6e\x55\x55\
'

patientCatPixels = b'\
\x11\x11\x11\x11\
\x11\x11\xff\x11\
\x11\x1f\x11\xf1\
\x11\x11\x11\x1f\
\x11\x11\x1f\xff\
\x1f\x11\xff\xff\
\x7f\x1f\xf1\x1f\
\xff\xff\x11\xf1\
'

zenCatPixels = b'\
\x11\xf1\xf1\x11\
\x11\x7f\x71\x11\
\xf1\xff\xf1\xf1\
\x1f\xff\xff\x11\
\x11\xff\xf1\x11\
\x1f\xff\xff\x61\
\x11\xf1\xf1\x16\
\x11\x11\x11\x61\
'

supermodelCatPixels = b'\
\x11\x17\x11\x11\
\x11\xc7\x11\x11\
\x11\x77\x11\x11\
\x11\x17\x71\x11\
\x11\x17\x71\x71\
\x11\x17\x71\x17\
\x11\x77\x77\x17\
\x11\x77\x71\x71\
'

unknownPixels = b'\
\x11\x11\x11\x11\
\x11\x1a\xa1\x11\
\x11\xa1\x1a\x11\
\x11\x11\x1a\x11\
\x11\x11\xa1\x11\
\x11\x1a\x11\x11\
\x11\x11\x11\x11\
\x11\x1a\x11\x11\
'

shufflePixels = b'\
\x11\x11\x11\x11\
\x11\x1a\xa1\x11\
\x11\xa1\x11\x11\
\xa1\xa1\x11\xa1\
\x1a\x11\x1a\x1a\
\x11\x11\x1a\x11\
\x11\x1a\xa1\x11\
\x11\x11\x11\x11\
'

powerNapPixels = b'\
\xcc\xc1\x11\x11\
\x11\xc1\x11\x11\
\x1c\x11\x11\x11\
\xc1\x11\x1c\xcc\
\xcc\xc1\x11\x1c\
\x11\x11\x11\xc1\
\x11\x11\x1c\x11\
\x11\x11\x1c\xcc\
'

shadowPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\xdd\xdd\x11\
\x1d\xdd\xdd\xd1\
\x11\xdd\xdd\x11\
\x11\x11\x11\x11\
'

leafPixels = b'\
\x11\x11\x33\x11\
\x11\x33\x3b\xb1\
\x13\x3b\xb3\x31\
\x13\xb3\x33\x31\
\x13\xb3\x33\x11\
\x1b\x33\x31\x11\
\x1b\x11\x11\x11\
\x11\xb1\x11\x11\
'

bugPixels = b'\
\x11\x1b\x31\x11\
\xd1\x32\x22\x1d\
\x1d\x12\x21\xd1\
\x11\xb3\x32\x11\
\xdd\x32\x22\xdd\
\x11\x22\x22\x11\
\x1d\x12\x21\xd1\
\xd1\x11\x11\x1d\
'

scruffyDogPixels = b'\
\x11\x11\x11\x11\
\x14\x44\x44\x41\
\x44\x44\x44\x44\
\x44\x44\x44\x44\
\x44\x44\x54\x54\
\x45\x44\x54\x54\
\x45\x55\x55\x54\
\x45\x5d\xd5\x54\
'

fastDogPixels = b'\
\x11\x11\x11\x11\
\x11\x61\x11\x16\
\x67\x61\x66\x16\
\x66\x66\x66\x61\
\x11\x66\x16\x61\
\x16\x11\x11\x66\
\x61\x11\x11\x16\
\x11\x11\x11\x11\
'

scaryDogPixels = b'\
\x11\x51\x15\x11\
\x15\x51\x15\x51\
\x15\x85\x58\x51\
\x15\x55\x55\x51\
\x15\x52\x25\x51\
\x11\x56\x56\x11\
\x11\x55\x55\x11\
\x15\x55\x55\x51\
'

slobberyDogPixels = b'\
\x11\x13\x74\x37\
\x1d\x44\x44\x77\
\x1d\x44\x44\x44\
\x11\x44\x44\x54\
\x11\x88\x55\x44\
\x18\x88\x44\x44\
\x18\x88\x11\x14\
\x11\x81\x11\x11\
'

laserPointerPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x88\x11\x11\
\x11\x88\x11\x11\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
\x11\x11\x11\x11\
'

fishmongerPixels = b'\
\x11\x55\x11\x11\
\x11\xff\x11\x11\
\x11\xff\x11\x11\
\xc4\xcc\x4c\xcc\
\xc4\xcc\x41\x16\
\xc4\x44\x41\x16\
\x14\x44\x41\x11\
\x15\x11\x51\x11\
'

tastyFishPixels = b'\
\x11\x11\x11\x11\
\x11\x11\x16\x61\
\x11\x66\x65\x61\
\x11\x16\x66\x61\
\x11\x66\x66\x11\
\x66\x66\x61\x11\
\x11\x61\x11\x11\
\x11\x61\x11\x11\
'

NAME = 0
COST = 1
TREATS = 2
POUNCES = 3
PATIENCE = 4
EXTRA = 5
ADOPTS = 6
DESC = 7
PIXELS = 8
SURFACE = 9

# Name, Cost, Treats, Pounces, Patience, Extra Cards, Adopt, Description, Pixels
friendlyCards = [\
['Sleepy Cat',1,0,1,0,0,0,'Recharging...',sleepyCatPixels],\
['Scruffy Cat',1,1,0,0,0,0,'Looks hungry',scruffyCatPixels],\
['Cute Cat',2,0,0,0,0,1,'Illegally adorable',cuteCatPixels],\
['Party Cat',4,0,1,0,1,0,'Here to party',partyCatPixels],\
['Chill Cat',4,1,0,0,1,0,'Here to chill',chillCatPixels],\
['Social Cat',4,0,0,0,2,0,'Brings friends',socialCatPixels],\
['Coffee Cat',3,0,2,0,0,0,'Woah now cat',coffeeCatPixels],\
['Wharf Cat',3,2,0,0,0,0,'Big tuna',wharfCatPixels],\
['Hunter Cat',5,0,3,0,0,0,'Master pouncer',hunterCatPixels],\
['Demanding Cat',5,3,0,0,0,0,'He wants snacks',demandingCatPixels],\
['Patient Cat',4,0,0,1,0,0,'Waiting to strike',patientCatPixels],\
['Zen Cat',5,0,0,2,0,0,'Unperturbed',zenCatPixels],\
['Supermodel Cat',4,0,0,0,0,2,'Star of the catwalk',supermodelCatPixels],\
['Power Nap',2,0,1,0,0,0,'Pounce fuel',powerNapPixels],\
['Randomize Shoppe',1,0,0,0,0,0,'Mix things up',shufflePixels],\
]

RANDOMIZE = len(friendlyCards)-1
POWER_NAP = RANDOMIZE-1

# Name, Cost, Treats, Pounces, Patience, Extra Cards, Adopt, Description
hostileCards = [\
['Shadow',3,0,0,0,0,0,'High definition',shadowPixels],\
['Leaf',4,0,0,-1,0,0,'Suspicious',leafPixels],\
['Bug',5,0,0,-2,0,0,'It moves funny',bugPixels],\
['Scruffy Dog',4,-1,0,0,0,0,'Will eat everything',scruffyDogPixels],\
['Fast Dog',4,0,-1,0,0,0,'Loves chasing',fastDogPixels],\
['Scary Dog',5,0,-1,-1,0,0,'Spooky',scaryDogPixels],\
['Slobbery Dog',5,-1,0,-1,0,0,'Slobbers on the treats!',slobberyDogPixels],\
['Laser Pointer',3,0,3,-2,0,0,'It always escapes',laserPointerPixels],\
['Fishmonger',3,3,0,-2,0,0,'Unfortunately likes dogs',fishmongerPixels],\
['Tasty Fish',2,1,0,0,0,0,'Worth pouncing',tastyFishPixels],\
['Randomize Enemies',1,0,0,0,0,0,'Try a new part of town',shufflePixels],\
]

RANDOMIZE2 = RANDOMIZE+len(hostileCards)
TASTY_FISH = RANDOMIZE2-1
9 Likes

You can just deliver the downloaded zip file. It should contain all the project settings too.

1 Like

Thanks, I had forgotten about that. Added.

Game’s idea is great but i found it hard to understand what was what. Where are those in-game instruction? All i saw is the info part at the bottom. Would be nice to see the other’s player patience during our turn too.

Maybe i just overlooked something? I am not new to deck-building games but making sense of all the info on screen is confusing me a little.

Edit: nevermind the part about instructions. I missed the C for instructions lol

1 Like

Due to the impending closure of PyInSky, I thought it was a good time for me to wrap up some unfinished business with Duel Paws. I had some balance changes that I had made that had just been sitting around, so I decided to take those and add a lot more changes on top of those in order to make a proper, polished version of Duel Paws. This version, v1.2.0, contains the following changes:

  • Rewritten and expanded instructions on how to play
  • New card guide showing all cards and their effects
  • Several gameplay rebalances
  • Various small changes to better convey information
  • View your deck and your opponent’s deck during a game
  • Added music and sound effects from OpenGameArt, all CC0:
    • Background music is “Talking Cute (Chiptune)” by Alex McCulloch.
    • Meow sound effect is from “Cat Purr & Meow” by Kerzoven.
    • All other SFX are from “Card Game sounds” by HaelDB.

I uploaded this new version on itch along with its corresponding source code.

After extracting the game zip, merge the “data” folder in the zip with the “data” folder at the root of your Pokitto’s micro SD card so that the music works. The .pop file can be placed anywhere on the micro SD card.

5 Likes