• borrax

    @ccc Thanks for the suggestions, I am new to python so I donโ€™t know all the tricks.

    The T_stacks and F_stacks are technically called the Tableu and Foundations, but I thought those were equally obsfucating and harder to type, so I didnโ€™t know what else to call them.

    posted in Pythonista read more
  • borrax

    Here is a simple solitaire game I wrote. It uses the Unicode playing card characters, so it wonโ€™t work on older devices that canโ€™t display them.

    from scene import *
    from random import *
    
    
    # a class to define the basic card behavior
    class Card(Node):
        # array of card icons. order is spades, clubs, hearts, diamonds
        card_icons = ['๐Ÿ‚ก', '๐Ÿ‚ข', '๐Ÿ‚ฃ', '๐Ÿ‚ค', '๐Ÿ‚ฅ', '๐Ÿ‚ฆ', '๐Ÿ‚ง', '๐Ÿ‚จ', '๐Ÿ‚ฉ', '๐Ÿ‚ช', '๐Ÿ‚ซ', '๐Ÿ‚ญ', '๐Ÿ‚ฎ', '๐Ÿƒ‘', '๐Ÿƒ’', '๐Ÿƒ“', '๐Ÿƒ”', '๐Ÿƒ•', '๐Ÿƒ–', '๐Ÿƒ—', '๐Ÿƒ˜', '๐Ÿƒ™', '๐Ÿƒš', '๐Ÿƒ›', '๐Ÿƒ', '๐Ÿƒž', '๐Ÿ‚ฑ', '๐Ÿ‚ฒ', '๐Ÿ‚ณ', '๐Ÿ‚ด', '๐Ÿ‚ต', '๐Ÿ‚ถ', '๐Ÿ‚ท', '๐Ÿ‚ธ', '๐Ÿ‚น', '๐Ÿ‚บ', '๐Ÿ‚ป', '๐Ÿ‚ฝ', '๐Ÿ‚พ', '๐Ÿƒ', '๐Ÿƒ‚', '๐Ÿƒƒ', '๐Ÿƒ„', '๐Ÿƒ…', '๐Ÿƒ†', '๐Ÿƒ‡', '๐Ÿƒˆ', '๐Ÿƒ‰', '๐ŸƒŠ', '๐Ÿƒ‹', '๐Ÿƒ', '๐ŸƒŽ']
        card_back = '๐Ÿ‚ '
        
        def __init__(self, card_number):
            '''init the card object'''
            # number is the 0 - 51 value for the card in the deck
            self.number = card_number
            # value is the 0 - 12 value in the suit
            self.value = self.number % 13
            # suit is the 0 - 3 value for the suit
            # 0 = spades 1 = clubs 2 = hearts 3 = daimonds
            self.suit = self.number // 13
            # set the icon based on the card number
            self.face = self.card_icons[self.number]
            # set the color based on card number. 
            if self.number < 26:
                self.color = 'black'
            else:
                self.color = 'red'
            
            # bool to determine if face up
            self.face_up = False
            # bool to determine if active
            self.active = False
                
            # set up the card image with nodes
            # the path defines the rectangular shape and size and line width
            rect_path = ui.Path.rect(0, 0, 75, 100)
            rect_path.line_width = 1
            # the bg_node is a shape node that uses the path to draw the shape
            bg_node = ShapeNode(rect_path, 'white', 'black')
            # the text node draws the unicode playing card on the bg_node
            self.text_node = LabelNode(self.card_back, ('Courier New', 100))
            if self.face_up:
                self.text_node.text = self.face
                self.text_node.color = self.color
            else:
                self.text_node.text = self.card_back
                self.text_node.color = 'blue'
            self.text_node.position = (0, 0)
            # add the nodes as children of the card node
            self.add_child(bg_node)
            bg_node.add_child(self.text_node)
            
        def point_inside(self, x, y):
            '''Check if the point x, y is within the bounding box'''
            return self.bbox.contains_point((x, y))
        
        def update(self):
            # Check if the card has been set to face up or not
            # Then set the text node and color accordingly
            if self.face_up:
                self.text_node.text = self.face
                self.text_node.color = self.color
            else:
                self.text_node.text = self.card_back
                self.text_node.color = 'blue'
    
    class Deck():
        # this object will hold all the cards needed for the game
        # all cards will be added to the deck during setup
        # then the deck will be shuffled, and one card will be taken
        # from the deck at a time
        def __init__(self):
            self.cards = []
            for i in range(0, 52):
                self.cards.append(Card(i))
        
        def shuffle(self):
            shuffle(self.cards)
            
        def print(self):
            for i in self.cards:
                print(i.face)
        
        def get_card(self):
            return self.cards.pop(0)
            
    class Pile(Node):
        # this class represents the pile of face down cards in the top
        # right corner. When clicked, it transfers a card to the waste
        def __init__(self):
            self.cards = []
            
            rect_path = ui.Path.rect(0, 0, 75, 100)
            rect_path.line_width = 1
            self.bg_node = ShapeNode(rect_path, 'white', 'black')
            self.label_node = LabelNode('๐Ÿ‚ ', ('Courier New', 100))
            self.label_node.color = 'blue'
            self.label_node.position = (0, 0)
            
            # If the pile has cards in it, draw a single face down card
            # if no cards, just draw a green rectangle
            if len(self.cards) == 0:
                self.bg_node.fill_color = 'green'
                self.label_node.text = ' '
            else:
                self.bg_node.fill_color = white
                self.label_node.text = '๐Ÿ‚ '
            
            self.add_child(self.bg_node)
            self.bg_node.add_child(self.label_node)
            
        def add_card(self, c):
            self.cards.insert(0, c)
            
        def get_card(self):
            return self.cards.pop(0)
            
        def update(self):
            # redraw the pile
            if len(self.cards) == 0:
                self.bg_node.fill_color = 'green'
                self.label_node.text = ' '
            else:
                self.bg_node.fill_color = 'white'
                self.label_node.text = '๐Ÿ‚ '
    
        def point_inside(self, x, y):
            return self.bbox.contains_point((x, y))
            
    class Waste(Node):
        # this class represents the stack of cards next to the pile
        # when clicked, it transfers a card to the moving stack
        def __init__(self):
            self.cards = []
            
            rect_path = ui.Path.rect(0, 0, 75, 100)
            rect_path.line_width = 1
            self.bg_node = ShapeNode(rect_path, 'green', 'black')
            self.text_node = LabelNode(' ', ('Courier New', 100))
            self.text_node.color = 'blue'
            
            self.add_child(self.bg_node)
            self.bg_node.add_child(self.text_node)
            
        def add_card(self, c):
            self.cards.insert(0, c)
            
        def get_card(self):
            return self.cards.pop(0)
            
        def update(self):
            # if no cards in the waste, draw a green rectangle
            # if cards are present, draw the top card face up
            if len(self.cards) == 0:
                self.bg_node.fill_color = 'green'
                self.text_node.text = ' '
            else:
                self.bg_node.fill_color = 'white'
                self.text_node.text = self.cards[0].face
                self.text_node.color = self.cards[0].color
                
        def point_inside(self, x, y):
            return self.bbox.contains_point((x, y))
                
    class F_stack(Node):
        # this class represents the stacks of cards in the top left where
        # all the cards go to win the game. There will be 4 of them.
        def __init__(self):
            self.cards = []
            self.suit = -1 # start with no suit
            
            rect_path = ui.Path.rect(0, 0, 75, 100)
            rect_path.line_width = 1
            self.bg_node = ShapeNode(rect_path, 'green', 'black')
            self.text_node = LabelNode(' ', ('Courier New', 100))
            
            self.add_child(self.bg_node)
            self.bg_node.add_child(self.text_node)
            
        def add_card(self, c):
            # if the suit has not been set yet, set it to what the card is
            # only accept cards with matching suit
            if self.suit == -1:
                self.cards.insert(0, c)
                self.suit = c.suit
            elif self.suit == c.suit:
                self.cards.insert(0, c)
            
        def get_card(self):
            return self.cards.pop(0)
            
        def point_inside(self, x, y):
            return self.bbox.contains_point((x, y))
            
        def update(self):
            # if the f stack has cards, draw the top card
            # if not, draw a green rectangle
            if len(self.cards) == 0:
                self.bg_node.fill_color = 'green'
                self.text_node.text = ' '
            else:
                self.bg_node.fill_color = 'white'
                self.text_node.text = self.cards[0].face
                self.text_node.color = self.cards[0].color
    
    
    class T_stack(Node):
        # this class represents the stacks of cards in the middle
        # of the game. Will hold both face up and face down cards.
        # When clicked, will need to determine which card in the 
        # stack was clicked and transfer the appropriate cards to
        # the moving stack
        def __init__(self):
            self.cards = []
            
            rect_path = ui.Path.rect(0, 0, 75, 100)
            rect_path.line_width = 1
            self.bg_node = ShapeNode(rect_path, 'green', 'black')
            self.add_child(self.bg_node)
            
        def add_card(self, c):
            # must also add each card as a child node so that it gets drawn.
            self.cards.append(c)
            self.add_child(c)
            
        def get_card(self):
            # must also remove card from children so that it stops being drawn.
            c = self.cards.pop()
            c.remove_from_parent()
            return c
            
        def point_inside(self, x, y):
            return self.bbox.contains_point((x, y))
            
        def get_touched_card(self, y):
            # cards are placed every 25 pixels down from the top of the
            # stack. This function takes the y coord of the touch location
            # and determines how many 25 pixel units fits between it and the
            # top of the bounding box. If the bottom card is touched, we might
            # calculate an out of bounds index, so we return the last valid 
            # index instead
            bbox_top = self.bbox[1] + self.bbox[3]
            i = round((bbox_top - y) // 25)
            return min(i, len(self.cards) - 1)
            
        def update(self):
            # set the position of each card in the stack
            # cards are placed every 25 pixels down
            for i in range(len(self.cards)):
                self.cards[i].position = (0, -i * 25)
                self.cards[i].z_position = i
                self.cards[i].update()
                
    class Stack(Node):
        # this class represents the cards being moved around the board
        # by the player.
        def __init__(self):
            self.cards = []
        
        def add_card(self, c):
            # all cards in the stack will be face up.
            # must add them to children so they are drawn
            c.face_up = True
            self.cards.insert(0, c)
            self.add_child(c)
            
        def get_card(self):
            # must remove from children so they are not drawn
            c = self.cards.pop(0)
            c.remove_from_parent()
            return c
        
        def update(self):
            # place cards every 25 pixels down from top
            for i in range(len(self.cards)):
                self.cards[i].position = (0, -i * 25)
                self.cards[i].z_position = i
                self.cards[i].update()
    
    class Bounce_card(Node):
        # this class represents the bouncing cards created at the end of a 
        # game. 
        def __init__(self, i, p, s):
            # i = the card to copy
            # p = the position to create the bounce card at
            # s = the size of the screen
            self.face = i.face
            self.color = i.color
            
            rect_path = ui.Path.rect(0, 0, 75, 100)
            self.bg_node = ShapeNode(rect_path, 'white', 'black')
            self.text_node = LabelNode(self.face, ('Courier New', 100))
            self.text_node.color = self.color
            self.add_child(self.bg_node)
            self.bg_node.add_child(self.text_node)
            self.position = p
            # randomly select speeds in the x and y directions
            self.x_speed = uniform(-5, 5)
            self.y_speed = uniform(-5, 5)
            self.size = s
            
        def update(self):
            # check if the bounding box is outside the scene
            # is so, reverse the speed so it comes back inside
            if self.bbox[0] < 0:
                self.x_speed *= -1
            if self.bbox[1] < 0:
                self.y_speed *= -1
            if self.bbox[0] + self.bbox[2] > self.size.w:
                self.x_speed *= -1
            if self.bbox[1] + self.bbox[3] > self.size.h:
                self.y_speed *= -1
            # create a move action with our speeds
            move_action = Action.move_by(self.x_speed, self.y_speed, 0, TIMING_LINEAR)
            self.run_action(move_action)
            
            
    class Reset(Node):
        # this class represents a reset button
        # when clicked, it will begin a new game
        def __init__(self):
            rect_path = ui.Path.rect(0, 0, 100, 75)
            self.bg_node = ShapeNode(rect_path, 'white', 'black')
            self.text_node = LabelNode('RESET', ('Helvetica', 28))
            self.text_node.color = 'black'
            self.bg_node.add_child(self.text_node)
            self.add_child(self.bg_node)
        
        def point_inside(self, x, y):
            return self.bbox.contains_point((x, y))
        
    class TestScene(Scene):
        def setup(self):
            self.background_color = 'green'
            
            # set up the deck object that initially holds all the cards
            # will be used to distribute cards to the other objects
            self.deck = Deck()
            self.deck.shuffle()
            
            # the pile is the stack of face down cards in the top right
            # when clicked, the top card will be transferred to waste
            # when empty, click will transfer all waste cards back to pile
            self.pile = Pile()
            self.pile.position = (self.size.w - 100, self.size.h - 100)
            self.add_child(self.pile)
            
            # holds face up cards that came from the pile
            # when clicked, will transfer top card to the stack
            self.waste = Waste()
            self.waste.position = (self.size.w - 200, self.size.h - 100)
            self.add_child(self.waste)
            
            # f stacks are the 4 sets of cards in the top left
            # when all cards are added to the f stacks the game is won
            # clicking an f stack should transfer the top card to the stack
            self.f_stacks = []
            for i in range(4):
                f = F_stack()
                f.position = (100 * (i + 1), self.size.h - 100)
                self.f_stacks.append(f)
                self.add_child(f)
            
            
            
            # t stacks are the 7 sets of cards in the middle
            # when clicking on the t stack, it should transfer all cards
            # below the click point to the stack
            self.t_stacks = []
            for i in range(7):
                t = T_stack()
                t.position = (100 * (i + 1), self.size.h - 250)
                # add a set number of face down cards to each t stack
                for j in range(i):
                    t.add_card(self.deck.get_card())
                # add 1 face up card to each t stack
                c = self.deck.get_card()
                c.face_up = True
                t.add_card(c)
                self.t_stacks.append(t)
                self.add_child(t)
            
            
            # put remaining deck cards into the pile
            for i in range(len(self.deck.cards)):
                self.pile.add_card(self.deck.get_card())
            
            # the stack is used to move cards between waste, t stacks, and f stacks
            # the stack should follow the touch when not empty
            # when a touch ends the cards should all be emptied from the stack
            # either to a new location or back to previous location
            self.stack = Stack()
            self.add_child(self.stack)
            
            self.bouncers = []
            self.win = False
            
            self.reset = Reset()
            self.reset.position = (self.size.w - 150, 100)
            self.add_child(self.reset)
        
    
        
        def touch_began(self, touch):
            x, y = touch.location
            # check if pile was clicked
            # if pile is not empty, transfer card to waste
            # if pile is empty, get all cards from waste
            if self.pile.point_inside(x, y):
                if len(self.pile.cards) > 0:
                    self.waste.add_card(self.pile.get_card())
                else:
                    for i in range(0, len(self.waste.cards)):
                        self.pile.add_card(self.waste.get_card())
            # check if waste was clicked
            # if waste is not empty, transfer top card to stack
            # if waste is empty, do nothing?
            if self.waste.point_inside(x, y):
                if len(self.waste.cards) > 0:
                    self.stack.card_return = self.waste
                    self.stack.add_card(self.waste.get_card())
                    move_action = Action.move_to(x, y, 0, TIMING_LINEAR)
                    self.stack.run_action(move_action)
            # check if T stacks were clicked
            # if the bottom card is face down, make it face up
            # otherwise, determine which card was touched and add the
            # cards below it to the moving stack 
            for t in self.t_stacks:
                if t.point_inside(x, y):
                    # make sure t stack is not empty
                    if len(t.cards) > 0:
                        # get the last card in the t stack
                        last_card = t.cards[-1]
                        # check if the last card is face up
                        if last_card.face_up:
                            # set this t stack as the card return so that
                            # the cards from the moving stack get put back
                            # in case the move is cancelled
                            self.stack.card_return = t
                            # get the index of the touched card
                            c = t.get_touched_card(y)
                            # if the touched card is face up, add it and all
                            # cards below it to the moving stack
                            if t.cards[c].face_up:
                                for i in range(c, len(t.cards)):
                                    self.stack.add_card(t.cards.pop())
                                # move the stack to the touched location.
                                move_action = Action.move_to(x, y, 0,TIMING_LINEAR)
                                self.stack.run_action(move_action)
                        # last card face down, make it face up.
                        else:
                            c = t.get_touched_card(y)
                            if c == len(t.cards) - 1:
                                last_card.face_up = True
                        
            
            # check if reset button is pressed.
            # reset game by removing all children from the scene
            # and running the setup function again.
            if self.reset.point_inside(x, y):
                for i in self.children:
                    i.remove_from_parent()
                self.setup()
                    
        def touch_moved(self, touch):
            x, y = touch.location
            # move the stack to the touch's current location
            if len(self.stack.cards) > 0:
                move_action = Action.move_to(x, y, 0, TIMING_LINEAR)
                self.stack.run_action(move_action)
                
        def touch_ended(self, touch):
            x, y = touch.location
            # check f stacks and t stacks.
            # if any are clicked, transfer cards from stack to target
            for f in self.f_stacks:
                if f.point_inside(x, y):
                    if len(self.stack.cards) == 1:
                    # check if empty and if stack is an ace
                        if len(f.cards) == 0:
                            if self.stack.cards[0].value == 0:
                                self.stack.card_return = f
                        # if not empty check that suit matches and that value is ok
                        else:
                            if f.suit == self.stack.cards[0].suit:
                                if f.cards[0].value == (self.stack.cards[0].value - 1):
                                    self.stack.card_return = f
                    
            # if the stack has cards and we end the touch on a t stack,
            # then check if the t stack has cards. If so, make sure that the
            # card we're adding to the t stack has an appropriate value and
            # color
            for t in self.t_stacks:
                if len(self.stack.cards) > 0:
                    if t.point_inside(x, y):
                        # check if the t stack is empty
                        if len(t.cards) > 0:
                            # check that first card in stack has the right value
                            stack_first = self.stack.cards[0]
                            t_stack_last = t.cards[len(t.cards) - 1]
                            if t_stack_last.value == (stack_first.value + 1):
                                # check suits
                                if (t_stack_last.suit == 0 or t_stack_last.suit == 1):
                                    if (stack_first.suit == 2 or stack_first.suit == 3):
                                        self.stack.card_return = t
                                elif (t_stack_last.suit == 2 or t_stack_last.suit == 3):
                                    if (stack_first.suit == 0 or stack_first.suit == 1):
                                        self.stack.card_return = t
                        # is empty, only allow kings
                        else:
                            if self.stack.cards[0].value == 12:
                                self.stack.card_return = t
                                
                
            
            # transfer cards from stack to card_return target
            for i in range(len(self.stack.cards)):
                self.stack.card_return.add_card(self.stack.get_card())
                
        def did_change_size(self):
            self.pile.position = (self.size.w - 100, self.size.h - 100)
            self.waste.position = (self.size.w - 200, self.size.h - 100)
            for i in range(len(self.f_stacks)):
                f = self.f_stacks[i]
                f.position = (100 * (i + 1), self.size.h - 100)
            for i in range(len(self.t_stacks)):
                t = self.t_stacks[i]
                t.position = (100 * (i + 1), self.size.h - 250)
            self.reset.position = (self.size.w - 150, 100)
            
        def update(self):
            self.pile.update()
            self.waste.update()
            for f in self.f_stacks:
                f.update()
            for t in self.t_stacks:
                t.update()
            self.stack.update()
            
            # check if won
            if self.win == False:
                # the game is won once the f stacks each have 13 cards
                if len(self.f_stacks[0].cards) == 13 and len(self.f_stacks[1].cards) == 13 and len(self.f_stacks[2].cards) == 13 and len(self.f_stacks[3].cards) == 13:
                    # create bouncers at each f stack location
                    for i in range(0, 13):
                        for f in self.f_stacks:
                            self.bouncers.append(Bounce_card(f.cards[i], f.position, self.size))
                            f.remove_from_parent()
                        
                    for i in self.bouncers:
                        self.add_child(i)
                        
                    self.win = True
                    
                
            for i in self.bouncers:
                i.update()
                    
                    
                    
    run(TestScene())
    

    posted in Pythonista read more
  • borrax

    @cvp, thanks, that worked.

    posted in Pythonista read more
  • borrax

    I wrote a simple solitaire game, everything works, but I would like to include a reset button. My first attempt failed, simply calling run() again did not work. So how to reset a scene?

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!