Python Forum
[Tkinter] Lambda seems to call expression 1 extra time every time it's called.
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Tkinter] Lambda seems to call expression 1 extra time every time it's called.
#1
Now, excuse if this is in the forum forum, even though I use a GUI, I don't beleive that's what's causing the problem.

The title makes this a little more confusing so let me explain.
I if do
a = lambda: some_function(16)
then call 'a' (I do this when a button is clicked, but that's beside the point). And 'some_function' is:
def some_function(some_number):
    print(some_number)
the output should be
Output:
>12
Then when 'a' is called again, it should again print
Output:
>12
However, with my code, I call 'a' once, and like expected it prints '12'. I then call it again, but this time, instead of printing '12' once, it print it twice. Like this:
Output:
#called once for 1st time >12 #called once for 2nd time >12 >12 #called once for 3rd time >12 >12 >12 .....
Even though the example is what happens, I will explain it with my real code, as it slightly different. When a button is pressed, a random number 1 through 6 is picked. When another button is pressed, it will call a function with parameters of either "P1" meaning player 1 or "P2" meaning player 2 and the random number. The player is changed every go (e.g. You press the button, a number is generated, you move your character. It becomes player 2's go. Player 2 does the same thing, it's now player 1's go).
This is how I do it:
a = lambda: self.move_players("P1" if self.currPl == 1 else "P2", random_number) #self.currPl evaluates to either 1 or two.
Now, on the first go, if player 1 rolles a 3, the function is called like
self.move_players("P1", 3)
Fine - it works. Then it becomes player 2's go. Let's say player 2 roll a 5, the function is, again, called like
self.move_players("P2", 5)
However, here lies the problem: if in the 'self.move_playersI(self, players, spaces)' function I do 'print(players, spaces)'. On player 1's first go it says
Output:
>P1, 3
Then on player 2's go it says
Output:
>P2, 3 >P2, 5
Remember how player 1 rolled a 3. It calls the function once with player 1's value for player 2, then again this time with player 2's actuall value. In some cases, this means player 2 moves double the intended spaces.
Why does this happen?
Is it the way I use lambda or the way I detect if a button is clicked?

Thanks in advance,
Dream

the full script can be found here: https://pastebin.com/yh5jxHFU
Full code
import sys
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5 import QtGui
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import time
import random
from guiLoop import guiLoop
from functools import partial

class Settings():
    SCENE_SIZE_X = 1200 #Settings for everything
    SCENE_SIZE_Y = 900
    GRID_COUNT_X = 4
    GRID_COUNT_Y = 5
    GRID_COUNT_TOTAL = GRID_COUNT_X * GRID_COUNT_Y
    GRID_BOX_WIDTH = 100
    GRID_BOX_HEIGHT = 100
    DEF_FONT_SIZE = 15
    DEF_PLAYER_SIZE = 25
    DEF_LEFT_PADDING = 5
    P1_TOP_PADDING = 10
    P2_TOP_PADDING = DEF_PLAYER_SIZE * 2 + 10
    ROLL_TIME = 20

class Drawing():
    p1 = QPen(QColor(66, 134, 244), 1, Qt.SolidLine) #p1 blue, #4286f4
    brushP1 = QBrush(QColor(66, 134, 244), Qt.SolidPattern)
    p2 = QPen(QColor(244, 65, 65), 1, Qt.SolidLine) #p2 red, #f44141
    brushP2 = QBrush(QColor(244, 65, 65), Qt.SolidPattern)
    transparent = QPen(QColor(255, 255, 255), 1, Qt.SolidLine)
    transparentB = QBrush(QColor(255, 255, 255), Qt.SolidPattern)

class Grid(QtWidgets.QGraphicsScene):

    def __init__(self):
        super().__init__()
        
        self.scrolled = 0
        self.ps = Players()
        self.texts = []
        self.initUI()

    def initUI(self):
        self.draw_grid(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT) #drawing grid first
        self.draw_text(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT, Settings.DEF_FONT_SIZE) #drawing text next
        rect = self.ps.draw_players("P1", 0)
        self.addRect(rect[0], Drawing.p1, Drawing.brushP1)
        rect = self.ps.draw_players("P2", 0)
        self.addRect(rect[1], Drawing.p2, Drawing.brushP2)

    def draw_grid(self, x_count, y_count, x_size, y_size):
        width = x_count * x_size
        height = y_count * y_size
        self.setSceneRect(0, 0, width, height)
        self.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)

        pen = QPen(QColor(0,0,0), 1, Qt.SolidLine)

        for x in range(0,x_count+1):    
            for y in range(0,y_count+1): #nested for loop for x and y (0,0) - (0,1) - (0,2) etc
                xc = x * x_size
                yc = y * y_size 
                self.addLine(xc,0,xc,height,pen)
                self.addLine(0,yc,width,yc,pen)

    def draw_text(self, x_count, y_count, x_size, y_size, font_size):
        font = QFont("Courier New", font_size, QFont.Bold)
        
        total = Settings.GRID_COUNT_TOTAL
        row_count = Settings.GRID_COUNT_Y
        text_pos_ev = []
        text_pos_od = []
        
        for y in range(0,y_count): #another nested for loop with out the +1 to y and x count
            for x in range(0,x_count):
                xcTEXT = x * x_size
                ycTEXT = y * y_size
                if row_count % 2 == 0: #if the row is even
                    text_pos_ev.append([xcTEXT, ycTEXT, total]) #add to even array
                else:
                    text_pos_od.append([xcTEXT, ycTEXT, total]) #otherwise add to odd array
                total = total - 1                
            row_count = row_count - 1

        for pos in text_pos_ev: #draws the even numbers
            text = self.addText(str(pos[2]), font)
            self.texts.append(text)
            text.setPos(pos[0], pos[1])
            text.setOpacity(0.6)
            
        new_text_pos_od = reverse_section(text_pos_od, 4) #flips the odd numbers so they snake around the board
        for pos in new_text_pos_od: #draws new odd numbers
            text = self.addText(str(pos[2]), font)
            self.texts.append(text)
            text.setPos(pos[0], pos[1])
            text.setOpacity(0.6)

    def move_players(self, player, spaces):
        rect = []
        if(player == "P1"):
            self.remove_player("P1")
            rect = self.ps.draw_players("P1", spaces)
            self.addRect(rect[0], Drawing.p1, Drawing.brushP1)
        elif(player == "P2"):
            self.remove_player("P2")
            rect = self.ps.draw_players("P2", spaces)
            self.addRect(rect[1], Drawing.p2, Drawing.brushP2)
        
    def remove_player(self, player):
        rect = self.ps.clear_old_player(player)
        self.addRect(rect, Drawing.transparent, Drawing.transparentB)
        self.redraw_text()

    def redraw_text(self):
        for text in self.texts:
            self.removeItem(text)
        del self.texts[:]
        self.draw_text(Settings.GRID_COUNT_X, Settings.GRID_COUNT_Y, Settings.GRID_BOX_WIDTH, Settings.GRID_BOX_HEIGHT, Settings.DEF_FONT_SIZE)
        
class Players(QRectF):
    def __init__(self):
        super().__init__()

        self.poses = []
        self.row_count = 0
        self.p1 = QRectF()
        self.p2 = QRectF()
        self.p1pos = 0
        self.p2pos = 0
        
        self.initUI()

    def initUI(self):
        self.gen_pos_arr()
 
    def draw_players(self, player, spaces): #space 1 is box 1, 2 is box 2, 3 is box 3..      
        if(player == "P1"):
            self.p1pos += spaces;
        elif(player == "P2"):
            self.p2pos += spaces;
        
        self.p1 = QRectF(self.poses[self.p1pos][0] + Settings.DEF_LEFT_PADDING, self.poses[self.p1pos][1] + Settings.P1_TOP_PADDING, Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE)
        self.p2 = QRectF(self.poses[self.p2pos][0] + Settings.DEF_LEFT_PADDING, self.poses[self.p2pos][1] + Settings.P2_TOP_PADDING, Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE)

        return [self.p1, self.p2]

    def clear_old_player(self, player):
        pos = []
        if(player == "P1"):
            pos = self.poses[self.p1pos]
            pos[0] += Settings.DEF_LEFT_PADDING
            pos[1] += Settings.P1_TOP_PADDING 
        elif(player == "P2"):
            pos = self.poses[self.p2pos]
            pos[0] += Settings.DEF_LEFT_PADDING 
            pos[1] += Settings.P2_TOP_PADDING

        return QRectF(pos[0], pos[1], Settings.DEF_PLAYER_SIZE, Settings.DEF_PLAYER_SIZE)
        

    def gen_pos_arr(self):
        y = Settings.GRID_COUNT_Y-1
        for i in range(1, Settings.GRID_COUNT_Y+1):
            if(y % 2 == 0):
                for x in range(0, Settings.GRID_COUNT_X):
                    self.poses.append([x*Settings.GRID_BOX_WIDTH,y*Settings.GRID_BOX_HEIGHT])
            else:
                for x in range(Settings.GRID_COUNT_X-1, -1, -1):
                    self.poses.append([x*Settings.GRID_BOX_WIDTH,y*Settings.GRID_BOX_HEIGHT])
            y = y - 1

class App(QtWidgets.QDialog):

    def __init__(self):
        super().__init__()
        
        self.images = ['1.png','2.png','3.png','4.png','5.png','6.png']
        self.image = QLabel(self)
        self.currTurn = "<font color='#4286f4'>Player 1's turn</font>"
        self.turn = QLabel(self)
        self.roll = QPushButton("Roll!")
        self.move = QPushButton("Move player")
        self.currPl = 1
        self.vbox1 = QVBoxLayout()
        self.vbox2 = QVBoxLayout()
        self.grid = Grid()
        self.initUI()

    def initUI(self):
        self.setGeometry(300, 100, Settings.SCENE_SIZE_X, Settings.SCENE_SIZE_Y)
        self.setWindowTitle("NEA Board Game")

        self.layout()

    @guiLoop
    def move_players(self, players, spaces):
        temp = 0

        print(players, spaces)
        
        self.move.setEnabled(False)
            
        self.turn.setText(self.currTurn)
        
        #self.grid.move_players(players, spaces)

        if(self.currPl == 1):
            while(temp < spaces):
                self.grid.move_players("P1", 1)
                temp += 1
                yield 0.5
        else:
            while(temp < spaces):
                self.grid.move_players("P2", 1)
                temp += 1
                yield 0.5
        
        if(self.currPl == 1):
            self.currPl = 2
            self.currTurn = "<font color='#f44141'>Player 2's turn</font>" 
            self.turn.setText(self.currTurn)
        elif(self.currPl == 2):
            self.currPl = 1
            self.currTurn = "<font color='#4286f4'>Player 1's turn</font>"
            self.turn.setText(self.currTurn)

        self.roll.setEnabled(True)
        spaces = 0
            


    def layout(self):
        gridGview = QtWidgets.QGraphicsView(self.grid)

        d = self.dice()
        
        layout = QGridLayout(self)
        layout.addWidget(d[1], 2, 1)
        layout.addWidget(d[0], 1, 1)
        layout.addWidget(gridGview, 0, 1)

    def dice(self):
        rollBox = QGroupBox("")
        font = QtGui.QFont("Courier New", 23, QtGui.QFont.Bold)
        
        self.roll.clicked.connect(self.diceRoll)
        
        self.turn.setText(self.currTurn)
        self.turn.setFont(font)
        self.turn.setAlignment(QtCore.Qt.AlignCenter)
        
        self.vbox1.addWidget(self.turn)
        self.vbox1.addWidget(self.roll)
        
        rollBox.setLayout(self.vbox1)

        diceBox = QGroupBox("")
        self.vbox2.addWidget(self.move)
        self.move.setEnabled(False)
        diceBox.setLayout(self.vbox2)
        

        return [rollBox, diceBox]

    @guiLoop
    def diceRoll(self, argument):
        temp = 0
        rollT = Settings.ROLL_TIME
        self.roll.setEnabled(False)
        while(temp <= rollT):
            yield ((temp * 0.02) + 0.05)
            img = random.choice(self.images)
            pixmap = QPixmap(img)
            self.image.setPixmap(pixmap)
            self.vbox2.addWidget(self.image)
            temp += 1
        self.move.setEnabled(True)
        a = lambda: self.move_players("P1" if self.currPl == 1 else "P2", int(img[0:1]))
        self.move.clicked.connect(a)
            
        
def reverse_section(arr, subsection):
    ml2 = [ (arr[i*subsection:(i+1)*subsection]) for i in range(int(len(arr)/subsection)) ]
    ml3 = [ sl[i][0:subsection-2] + [sl[len(sl)-i-1][2]] for sl in ml2 for i in range(len(sl)) ]
    return ml3


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    app.setStyle("Fusion")
    ex = App()
    ex.show()
    sys.exit(app.exec_())
Reply


Messages In This Thread
Lambda seems to call expression 1 extra time every time it's called. - by DreamingInsanity - May-16-2019, 04:40 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Doubt approach update 2 Treeview at the same time TomasSanchexx 7 2,054 Sep-19-2023, 01:19 AM
Last Post: SaintOtis12
  Figure Gets Larger Every time I click a show button joshuagreineder 2 1,356 Aug-11-2022, 06:25 AM
Last Post: chinky
  tkinter get method is not accepting value when called by function jagasrik 1 2,571 Sep-16-2020, 05:28 AM
Last Post: Yoriz
  How to stop time counter in Tkinter LoneStar 1 4,446 Sep-11-2020, 08:56 PM
Last Post: Yoriz
  Display text 3 words at a time algae 5 2,813 Jun-27-2020, 10:25 AM
Last Post: menator01
Photo Visualizing time series data of graph nodes in plotly deepa 3 3,535 Mar-16-2020, 07:06 AM
Last Post: Larz60+
  [Tkinter] How to adjust time - tkinter Ondrej 2 2,901 Jun-20-2019, 05:53 PM
Last Post: Yoriz
  [Tkinter] Unable to create checkbox and select at run time tej7gandhi 5 4,731 May-05-2019, 04:57 PM
Last Post: tej7gandhi
  Buttons not appearing, first time button making admiral_hawk 5 3,434 Dec-31-2018, 03:26 AM
Last Post: admiral_hawk
  clear all widgets at same time (not delete/remove) shift838 0 2,779 Dec-17-2018, 11:55 PM
Last Post: shift838

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020