|
| 1 | +# This my first app done using REMI and Python. Probably there is a better way to do it. |
| 2 | +# This app is a simple scoreboard. I use it when I play pool/biliard. |
| 3 | +# In the app is possible set-up how many Games one has to win, to win a Match. |
| 4 | +# Example: the program starts with 5 games to win for a match. The background color of the window of each player is normally green, when one player arrives |
| 5 | +# to 4 games won his background becomes orange (it's a sign that he needs only one more game to win the match). When you arrive to 5 your background |
| 6 | +# becomes red for 3 seconds and then it goes back to green, the games score goes back to 0 and your "matches won" increase of one. |
| 7 | + |
| 8 | +import remi.gui as gui |
| 9 | +from remi import start, App |
| 10 | +import os |
| 11 | +import time |
| 12 | +import threading |
| 13 | + |
| 14 | +class MyApp(App): |
| 15 | + |
| 16 | + # I modified the original CSS file adding a "Button:hover" property and other color schemes; the main configuraton of the program has been done directly in the code. |
| 17 | + # Add the new CSS file in a /RES folder. |
| 18 | + def __init__(self, *args): |
| 19 | + res_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'res') |
| 20 | + super(MyApp, self).__init__(*args, static_file_path=res_path) |
| 21 | + |
| 22 | + |
| 23 | + def idle(self): |
| 24 | + pass |
| 25 | + |
| 26 | + # This is the function called as a new thread that updates the Number displayed, change the background color to red for 3 seconds, reset the game variables, |
| 27 | + # change the background color back to green. |
| 28 | + def ChangeColor(self, Text, Side, Num, BtUp, BtDn): |
| 29 | + self.In = 1 |
| 30 | + with self.update_lock: |
| 31 | + Text.set_text(Num) |
| 32 | + Side.style['background-color'] = 'red' |
| 33 | + BtUp.attributes['class']='up80red' |
| 34 | + BtDn.attributes['class']='dn80red' |
| 35 | + time.sleep(3) # I tried to make this function without use the thread but the "sleep" here frozen the interface update and I wasn't able to change |
| 36 | + # the background to red. Remi's guys helped me to solve this. |
| 37 | + with self.update_lock: |
| 38 | + Side.style['background-color'] = 'green' |
| 39 | + BtUp.attributes['class']='up80' |
| 40 | + BtDn.attributes['class']='dn80' |
| 41 | + self.LeftNum = 0 |
| 42 | + self.RightNum = 0 |
| 43 | + self.In = 0 |
| 44 | + self.check_score() |
| 45 | + |
| 46 | + def main(self): |
| 47 | + self.In = 0 # Used to disable all buttons when in thread function, otherwise strange things happen if you press a button when the app is in the thread |
| 48 | + self.LeftNum = 0 # Left player game won |
| 49 | + self.RightNum = 0 # Right player game won |
| 50 | + self.MatchNum = 5 # Number of game to win for each match |
| 51 | + self.LeftMatchNum = 0 # Left player match won |
| 52 | + self.RightMatchNum = 0 # Right player match won |
| 53 | + self.Name1 = 'LEFT' # Name left player |
| 54 | + self.Name2 = 'RIGHT' # Name right player |
| 55 | + |
| 56 | + |
| 57 | + # Main container configuration page |
| 58 | + widMenu = gui.Widget(width=480, height=610, layout_orientation=gui.Widget.LAYOUT_VERTICAL, style={'margin':'0px auto', 'background':'black'}) |
| 59 | + # Configuration menu |
| 60 | + self.lblMenu = gui.Label('SCOREBOARD', width='100%', height='45px', style={'margin':'0px 0px 0px', 'padding-top':'10px', 'font-size':'40px', 'font-weight':'bold', 'color':'green', 'line-height':'45px', 'text-align':'center'}) |
| 61 | + self.lblMenu2 = gui.Label('Setup players name:', width='100%', height='45px', style={'margin':'0px 0px 0px', 'padding-top':'10px', 'font-size':'30px', 'font-weight':'bold', 'line-height':'45px', 'text-align':'left'}) |
| 62 | + self.lblName1 = gui.Label('PLAYER 1 NAME:', width='100%', height='35px', style={'margin':'0px 0px 0px', 'padding-top':'20px', 'font-size':'20px', 'line-height':'25px', 'text-align':'left'}) |
| 63 | + self.txtName1 = gui.TextInput(width='96%', height='35px', style={'margin':'0px auto', 'padding-top':'20px', 'padding-left':'5px', 'font-size':'30px', 'line-height':'20px', 'text-align':'left', 'border':'1px solid white', 'background':'black'}) |
| 64 | + self.txtName1.set_text('P1') |
| 65 | + self.lblName2 = gui.Label('PLAYER 2 NAME:', width='100%', height='35px', style={'margin':'0px 0px 0px', 'padding-top':'20px', 'font-size':'20px', 'line-height':'25px', 'text-align':'left'}) |
| 66 | + self.txtName2 = gui.TextInput(width='96%', height='35px', style={'margin':'0px auto', 'padding-top':'20px', 'padding-left':'5px', 'font-size':'30px', 'line-height':'20px', 'text-align':'left', 'border':'1px solid white', 'background':'black'}) |
| 67 | + self.txtName2.set_text('P2') |
| 68 | +## self.lblMatchSet = gui.Label('RACE TO:', width='100%', height='35px', style={'margin':'0px 0px 0px', 'padding-top':'20px', 'font-size':'20px', 'line-height':'25px', 'text-align':'left'}) |
| 69 | +## self.txtMatchSet = gui.TextInput(width='100%', height='35px', style={'margin':'0px 0px 0px', 'padding-top':'20px', 'font-size':'30px', 'line-height':'20px', 'text-align':'left', 'border':'1px solid white', 'background':'black'}) |
| 70 | +## self.txtMatchSet.set_text('5') |
| 71 | + # Start button |
| 72 | + btMenu = gui.Button('START', width='40%', height='40px', style={'margin':'50px 20% 20px', 'font-size':'30px', 'line-height':'30px', 'text-align':'center'}) |
| 73 | +## widMenu.append([self.lblMenu, self.lblMenu2, self.lblName1, self.txtName1, self.lblName2, self.txtName2, self.lblMatchSet, self.txtMatchSet, btMenu]) |
| 74 | + widMenu.append([self.lblMenu, self.lblMenu2, self.lblName1, self.txtName1, self.lblName2, self.txtName2, btMenu]) |
| 75 | + # Buttons function call |
| 76 | + btMenu.onclick.connect(self.on_button_pressed_menu) |
| 77 | + |
| 78 | + |
| 79 | + # Main container scoreboard page |
| 80 | + wid = gui.Widget(width=480, height=610, style={'margin':'0px auto', 'background':'black'}) |
| 81 | + # Title |
| 82 | + self.lbl = gui.Label('SCOREBOARD', width='100%', height='35px', style={'margin':'0px 0px 0px', 'padding-top':'10px', 'font-size':'30px', 'line-height':'35px', 'text-align':'center'}) |
| 83 | + # Containers for games counters |
| 84 | + # Horizontal container |
| 85 | + wid1 = gui.Widget(width='100%', height=600, layout_orientation=gui.Widget.LAYOUT_HORIZONTAL, style={'background':'black'}) |
| 86 | + # Container for left side |
| 87 | + self.wid2 = gui.Widget(width=230, height=350, margin='5px', style={'background':'green'}) |
| 88 | + # Container for right side |
| 89 | + self.wid3 = gui.Widget(width=230, height=350, margin='5px', style={'background':'green'}) |
| 90 | + # Left side interface |
| 91 | + self.lblLeftName = gui.Label(self.Name1, width='95%', height='60px', style={'margin':'20px 2px 0px', 'font-size':'40px', 'line-height':'60px', 'text-align':'center', 'overflow':'hidden'}) |
| 92 | + self.lblLeftNum = gui.Label(self.LeftNum, width='100%', height='130px', style={'margin':'0px 0px 10px', 'font-size':'140px', 'line-height':'130px', 'text-align':'center'}) |
| 93 | + self.btLeftPlus = gui.Button('', width='80px', height='80px', style={'margin':'0px 10px 20px', 'font-size':'50px', 'line-height':'50px', 'text-align':'center'}) |
| 94 | + self.btLeftPlus.attributes['class']='up80' |
| 95 | + self.btLeftMinus = gui.Button('', width='80px', height='80px', style={'margin':'0px 10px 20px', 'font-size':'50px', 'line-height':'50px', 'text-align':'center'}) |
| 96 | + self.btLeftMinus.attributes['class']='dn80' |
| 97 | + lblLeftMatch = gui.Label('MATCHES WON:', width=150, height='30px', style={'margin':'0px 5px', 'font-size':'20px', 'line-height':'30px', 'text-align':'left', 'display':'inline'}) |
| 98 | + self.lblLeftMatches = gui.Label(self.LeftMatchNum, width=30, height='30px', style={'margin':'0px 5px', 'font-size':'20px', 'line-height':'30px', 'text-align':'left', 'display':'inline'}) |
| 99 | + # Right side interface |
| 100 | + self.lblRightName = gui.Label(self.Name2, width='95%', height='60px', style={'margin':'20px 2px 0px', 'font-size':'40px', 'line-height':'60px', 'text-align':'center', 'overflow':'hidden'}) |
| 101 | + self.lblRightNum = gui.Label(self.LeftNum, width='100%', height='130px', style={'margin':'0px 0px 10px', 'font-size':'140px', 'line-height':'130px', 'text-align':'center'}) |
| 102 | + self.btRightPlus = gui.Button('', width='80px', height='80px', style={'margin':'0px 10px 20px', 'font-size':'50px', 'line-height':'50px', 'text-align':'center'}) |
| 103 | + self.btRightPlus.attributes['class']='up80' |
| 104 | + self.btRightMinus = gui.Button('', width='80px', height='80px', style={'margin':'0px 10px 20px', 'font-size':'50px', 'line-height':'50px', 'text-align':'center'}) |
| 105 | + self.btRightMinus.attributes['class']='dn80' |
| 106 | + lblRightMatch = gui.Label('MATCHES WON:', width=150, height='30px', style={'margin':'0px 5px', 'font-size':'20px', 'line-height':'30px', 'text-align':'left', 'display':'inline'}) |
| 107 | + self.lblRightMatches = gui.Label(self.RightMatchNum, width=30, height='30px', style={'margin':'0px 5px', 'font-size':'20px', 'line-height':'30px', 'text-align':'left', 'display':'inline'}) |
| 108 | + # Appends all the widgets to create the interface |
| 109 | + self.wid2.append([self.lblLeftName, self.lblLeftNum, self.btLeftPlus, self.btLeftMinus, lblLeftMatch, self.lblLeftMatches]) |
| 110 | + self.wid3.append([self.lblRightName, self.lblRightNum, self.btRightPlus, self.btRightMinus, lblRightMatch, self.lblRightMatches]) |
| 111 | + wid1.append(self.wid2) |
| 112 | + wid1.append(self.wid3) |
| 113 | + # Extra labels and button to manage: |
| 114 | + # The number of games to win, to win a match |
| 115 | + lblMatch = gui.Label('GAMES FOR MATCH:', width='50%', height='50px', style={'margin':'15px 2px 0px 10px', 'font-size':'25px', 'line-height':'35px', 'text-align':'center'}) |
| 116 | + self.lblMatches = gui.Label(self.MatchNum, width='8%', height='50px', style={'margin':'15px 2px 0px', 'font-size':'25px', 'line-height':'35px', 'text-align':'center'}) |
| 117 | + btMatchPlus = gui.Button('', width='50px', height='50px', style={'margin':'5px 2px 0px 20px', 'font-size':'30px', 'line-height':'30px', 'text-align':'center'}) |
| 118 | + btMatchPlus.attributes['class']='up50' |
| 119 | + btMatchMinus = gui.Button('', width='50px', height='50px', style={'margin':'5px 2px', 'font-size':'30px', 'line-height':'30px', 'text-align':'center'}) |
| 120 | + btMatchMinus.attributes['class']='dn50' |
| 121 | + wid1.append([lblMatch, btMatchPlus, self.lblMatches, btMatchMinus]) |
| 122 | + # Reset buttons for Score and Matches won |
| 123 | + btReset = gui.Button('RESET SCORE', width='50%', height='35px', style={'margin':'10px 25% 10px', 'font-size':'25px', 'line-height':'30px', 'text-align':'center'}) |
| 124 | + wid1.append(btReset) |
| 125 | + btResetMatch = gui.Button('RESET MATCH', width='50%', height='35px', style={'margin':'10px 25% 10px', 'font-size':'25px', 'line-height':'30px', 'text-align':'center'}) |
| 126 | + wid1.append(btResetMatch) |
| 127 | + btSetting = gui.Button('SETTINGS', width='50%', height='35px', style={'margin':'10px 25% 20px', 'font-size':'25px', 'line-height':'30px', 'text-align':'center'}) |
| 128 | + wid1.append(btSetting) |
| 129 | + # Buttons function call |
| 130 | + # 'LT', 'RT', 'PLUS', 'MINUS' are used to identify the button pressed; in this way I created a single function for Left and Right buttons. |
| 131 | + self.btLeftPlus.onclick.connect(self.on_button_pressed_plus, 'LT') |
| 132 | + self.btLeftMinus.onclick.connect(self.on_button_pressed_minus, 'LT') |
| 133 | + self.btRightPlus.onclick.connect(self.on_button_pressed_plus, 'RT') |
| 134 | + self.btRightMinus.onclick.connect(self.on_button_pressed_minus, 'RT') |
| 135 | + btMatchPlus.onclick.connect(self.on_button_pressed_match, 'PLUS') |
| 136 | + btMatchMinus.onclick.connect(self.on_button_pressed_match, 'MINUS') |
| 137 | + btReset.onclick.connect(self.on_button_pressed_reset) |
| 138 | + btResetMatch.onclick.connect(self.on_button_pressed_reset_match) |
| 139 | + btSetting.onclick.connect(self.on_button_setting) |
| 140 | + # Append the Titleand the interface to the main container |
| 141 | + wid.append(self.lbl) |
| 142 | + wid.append(wid1) |
| 143 | + |
| 144 | + self.wid = wid |
| 145 | + self.widMenu = widMenu |
| 146 | + # Returning the configuration page |
| 147 | + return self.widMenu |
| 148 | + |
| 149 | + #Used to change the size of the font based on the number of characters of the name |
| 150 | + @staticmethod |
| 151 | + def name_length(Name): |
| 152 | + if len(Name) <= 6: |
| 153 | + return (Name, 40) |
| 154 | + elif len(Name) <= 8: |
| 155 | + return (Name, 30) |
| 156 | + elif len(Name) <= 10: |
| 157 | + return (Name, 22) |
| 158 | + else: |
| 159 | + Name = Name[:14] #always cuts the name at 14 characters |
| 160 | + return (Name, 22) |
| 161 | + |
| 162 | + #Used to setup the name typed in setting window in the scoreboard, and activate the main widget |
| 163 | + def on_button_pressed_menu(self, emitter): |
| 164 | + # left name |
| 165 | + Name = self.txtName1.get_text() |
| 166 | + Name, FntSize = MyApp.name_length(Name) |
| 167 | + FntSize = str(FntSize) + "px" |
| 168 | + self.lblLeftName.style['font-size'] = FntSize |
| 169 | + self.lblLeftName.set_text(Name) |
| 170 | + # right name |
| 171 | + Name = self.txtName2.get_text() |
| 172 | + Name, FntSize = MyApp.name_length(Name) |
| 173 | + FntSize = str(FntSize) + "px" |
| 174 | + self.lblRightName.style['font-size'] = FntSize |
| 175 | + self.lblRightName.set_text(Name) |
| 176 | + |
| 177 | +## self.lblMatches.set_text(self.txtMatchSet.get_text()) |
| 178 | +## self.MatchNum = int(self.txtMatchSet.get_text()) |
| 179 | + self.set_root_widget(self.wid) |
| 180 | + |
| 181 | + #Used to activate the setting widget |
| 182 | + def on_button_setting(self, emitter): |
| 183 | + self.set_root_widget(self.widMenu) |
| 184 | + |
| 185 | + def check_score(self): |
| 186 | + # Here the software update automatically any number you can see in the app |
| 187 | + if (self.LeftNum < self.MatchNum) and (self.RightNum < self.MatchNum): |
| 188 | + self.lblLeftNum.set_text(self.LeftNum) |
| 189 | + self.lblRightNum.set_text(self.RightNum) |
| 190 | + self.lblLeftMatches.set_text(self.LeftMatchNum) |
| 191 | + self.lblRightMatches.set_text(self.RightMatchNum) |
| 192 | + self.lblMatches.set_text(self.MatchNum) |
| 193 | + # Here the software check if a background needs to be green or orange. |
| 194 | + if (self.LeftNum < self.MatchNum - 1): |
| 195 | + self.wid2.style['background-color'] = 'green' |
| 196 | + self.btLeftPlus.attributes['class']='up80' |
| 197 | + self.btLeftMinus.attributes['class']='dn80' |
| 198 | + if (self.RightNum < self.MatchNum - 1): |
| 199 | + self.wid3.style['background-color'] = 'green' |
| 200 | + self.btRightPlus.attributes['class']='up80' |
| 201 | + self.btRightMinus.attributes['class']='dn80' |
| 202 | + if (self.LeftNum == self.MatchNum - 1): |
| 203 | + self.wid2.style['background-color'] = 'orange' |
| 204 | + self.btLeftPlus.attributes['class']='up80org' |
| 205 | + self.btLeftMinus.attributes['class']='dn80org' |
| 206 | + if (self.RightNum == self.MatchNum - 1): |
| 207 | + self.wid3.style['background-color'] = 'orange' |
| 208 | + self.btRightPlus.attributes['class']='up80org' |
| 209 | + self.btRightMinus.attributes['class']='dn80org' |
| 210 | + # When one of the player win the match a thread is called to temporary convert the background to red and then move it back to green. |
| 211 | + # The thread is required to don't stop the automatic update of the graphics in the app. |
| 212 | + # The app passes to the thread three parameters: lblLeftNum (used as text field to show the number in the app), the widget where the information are on the interface, |
| 213 | + # LeftNum that is the varible to check the games won fro the left player). For right player is the same but instead of left in the varible I used right :-). |
| 214 | + |
| 215 | + # Left side |
| 216 | + if (self.LeftNum >= self.MatchNum): |
| 217 | + Side = [self.lblLeftNum, self.wid2, self.LeftNum, self.btLeftPlus, self.btLeftMinus] |
| 218 | + t = threading.Thread(target=self.ChangeColor, args = (Side)) |
| 219 | + t.start() |
| 220 | + self.LeftMatchNum = self.LeftMatchNum + 1 |
| 221 | + # Right side |
| 222 | + elif (self.RightNum >= self.MatchNum): |
| 223 | + Side = [self.lblRightNum, self.wid3, self.RightNum, self.btRightPlus, self.btRightMinus] |
| 224 | + t = threading.Thread(target=self.ChangeColor, args = (Side)) |
| 225 | + t.start() |
| 226 | + self.RightMatchNum = self.RightMatchNum + 1 |
| 227 | + |
| 228 | + # Each function use the Side parameter to identify who called the function and conseguently what update (maybe there is a different way to manage this). |
| 229 | + # Increase the number of the games won |
| 230 | + def on_button_pressed_plus(self, emitter, Side): |
| 231 | + if not self.In: |
| 232 | + if Side == 'LT': |
| 233 | + if self.LeftNum < self.MatchNum: |
| 234 | + self.LeftNum = self.LeftNum + 1 |
| 235 | + elif Side == 'RT': |
| 236 | + if self.RightNum < self.MatchNum: |
| 237 | + self.RightNum = self.RightNum + 1 |
| 238 | + self.check_score() |
| 239 | + |
| 240 | + # Decrease the number of the games won |
| 241 | + def on_button_pressed_minus(self, emitter, Side): |
| 242 | + if not self.In: |
| 243 | + if Side == 'LT': |
| 244 | + if self.LeftNum != 0: |
| 245 | + self.LeftNum = self.LeftNum - 1 |
| 246 | + elif Side == 'RT': |
| 247 | + if self.RightNum != 0: |
| 248 | + self.RightNum = self.RightNum - 1 |
| 249 | + self.check_score() |
| 250 | + |
| 251 | + # Increase or decrease the Matches number |
| 252 | + def on_button_pressed_match(self, emitter, Side): |
| 253 | + if not self.In: |
| 254 | + if Side == 'PLUS': |
| 255 | + self.MatchNum = self.MatchNum + 1 |
| 256 | + elif Side == 'MINUS': |
| 257 | + # When the user decrease the number of Matches to win, in case this became lower than the actual games won by each player, automatically the number of games |
| 258 | + # won decrease too. It's a way to never have a number of games won bigger than the number of matches needed to win. |
| 259 | + # Try this in the app to better understand my explanation. With Match set-up to five, increase the game won of one player to three and then go down |
| 260 | + # with the Matches button to 1 and see what happen. |
| 261 | + if self.MatchNum > 1: |
| 262 | + if self.MatchNum - 1 <= self.LeftNum: |
| 263 | + self.LeftNum = self.LeftNum - 1 |
| 264 | + if self.MatchNum - 1 <= self.RightNum: |
| 265 | + self.RightNum = self.RightNum - 1 |
| 266 | + self.MatchNum = self.MatchNum - 1 |
| 267 | + self.check_score() |
| 268 | + |
| 269 | + def on_button_pressed_reset(self, emitter): |
| 270 | + if not self.In: |
| 271 | + self.LeftNum = 0 |
| 272 | + self.RightNum = 0 |
| 273 | + self.check_score() |
| 274 | + |
| 275 | + def on_button_pressed_reset_match(self, emitter): |
| 276 | + if not self.In: |
| 277 | + self.LeftMatchNum = 0 |
| 278 | + self.RightMatchNum = 0 |
| 279 | + self.check_score() |
| 280 | + |
| 281 | + |
| 282 | +if __name__ == "__main__": |
| 283 | + # starts the webserver |
| 284 | + # optional parameters |
| 285 | + start(MyApp,address='', port=8081, multiple_instance=False,enable_file_cache=True, update_interval=0.1, start_browser=True) |
| 286 | + # start(MyApp, debug=True) |
0 commit comments