I released a 'How to' video for the 'RPG Systems - simple input validation' post.
find the video here:
the post here:
Please note that the post was released a while before the video so the code has changed a little.
This blog is about video games and the artistic development process behind them. It will focus on my work and growth as an artist in the game development pipeline. The majority of content will be more "technical Art", modeling, texturing, and maybe rigging. There should also be a good amount of traditional art, and cake
I released a 'How to' video for the 'RPG Systems - simple input validation' post.
find the video here:
the post here:
Please note that the post was released a while before the video so the code has changed a little.
#Python 3 - Win10
import tkinter as tk def inspect_wrapline(event=None): start = f"{tk.INSERT} linestart" end = f"{tk.INSERT} lineend" counter = event.widget.count(start, end, "displaylines") print("number of lines after first line", counter) start = f"{tk.INSERT} display linestart" end = f"{tk.INSERT} display lineend +1c" dline = event.widget.get(start, end) print("Text on the displayline", dline) root_window = tk.Tk() text_widget = tk.Text(root_window) text_widget.pack() text_widget.insert("This is an example text.") text_widget.bind("<Control-l>", inspect_wrapline) root_window.mainloop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
import time import os __IMPORTS__ = dir() class Game_App(): """Game application doc_string""" # =================================== # CLASS OBJECT CONSTRUCTOR def __init__(self, *args, **kwargs): self.delay_clear_console() self.game_loop() # CLASS OBJECT CONSTRUCTOR # =================================== # =================================== # PLAYER INPUT HANDLING & Display # Fluff so we can see code output @staticmethod def delay_clear_console(delay=0): try: time.sleep(int(delay)) except: pass os.system('cls' if os.name == 'nt' else 'clear') def action_options(self, actions_dict, div_space=" - "): for action_index, action_text in enumerate(actions_dict): print(f"{action_index+1}{div_space}{action_text}") def validate_choice(self, choice_str, actions_dict): if choice_str == '': return None validated_choice = None if choice_str.isdigit(): option_num = int(choice_str) if option_num < len(actions_dict)+1: validated_choice = (choice_str, list(actions_dict)[option_num-1]) else: action_selected = [ (action_index, action) for action_index, action in enumerate(actions_dict) if choice_str in action ] if [] != action_selected: validated_choice = (action_selected[0][0], action_selected[0][1]) if tuple != type(validated_choice) or () == validated_choice: validated_choice = None return validated_choice def standard_input(self, prompt:str, optionText_actionFunc_dict:{'key':'func'}) -> (('index', 'key'), {'key':'func'}): action_display_order = optionText_actionFunc_dict action_key_value = None while None == action_key_value: print(f"Player x's turn:") print("--------------------") self.action_options(action_display_order, " - ") print("--------------------") print(f"{prompt}:") option_selected = input(">>").lower() action_key_value = self.validate_choice(option_selected, action_display_order) if None == action_key_value: print() print(f"<{option_selected}> is not valid, enter option number or name from list.") self.delay_clear_console(2) self.delay_clear_console() return action_key_value, action_display_order # PLAYER INPUT HANDLING # =================================== def game_loop(self): running = True while running: self.delay_clear_console() # Clear previous 'frame' selected_option, options = self.standard_input( "selection option by name or number", {'exit':"self.exit_app", 'leave game':"self.exit_game", 'end turn':"self.end_turn", 'menu':"self.open_menu", 'wait':"self.order_unit_wait", 'status':"self.status", 'move':"self.order_unit_move", 'act':"self.order_unit_action" } ) # Visual Debug of output print(selected_option) # . . . Do things with the response Here . . . # End of loop self.delay_clear_console(2) # Loop termination condition if 'exit' == selected_option[1]: running = False # End of application print("Don't go the drones need you. They look up to you.") self.delay_clear_console(3) if __name__ == "__main__": game_catch = Game_App() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
from random import randint class character(): def __init__(self, **keywargs): self.name = keywargs['name'] if 'name' in keywargs else "Hero" self.lvl = keywargs['lvl'] if 'lvl' in keywargs else 1 self.xp = keywargs['xp'] if 'xp' in keywargs else 0 self.lvl_next = keywargs['lvl_next'] if 'lvl_next' in keywargs else 25 self.str = keywargs['str'] if 'str' in keywargs else 1 self.dex = keywargs['dex'] if 'dex' in keywargs else 1 self.int = keywargs['int'] if 'int' in keywargs else 1 self.hp = keywargs['hp'] if 'hp' in keywargs else 30 self.atk = keywargs['atk'] if 'atk' in keywargs else [5, 12] self.xp_reward = keywargs['xp_reward'] if 'xp_reward' in keywargs else 25 self.loot_reward = keywargs['loot_reward'] if 'loot_reward' in keywargs else ("gold", 3) def update_level(self): if self.xp < self.lvl_next: return None new_str, new_dex, new_int = 0, 0, 0 init_lvl = self.lvl # Updated code: # Differences in accessing values # char is now self # Class vs Dictionary # self.xp vs. char['xp'] # V V # 25+ 25+ while self.xp >= self.lvl_next: self.lvl += 1 self.xp = self.xp - self.lvl_next self.lvl_next = round(self.lvl_next * 1.5) new_str += 1 new_dex += 1 new_int += 1 print() lvl_gained = self.lvl - init_lvl plural = "level" if 1 < lvl_gained: plural = "levels" print(f"{self.name} has gained {lvl_gained} {plural}!") print("====================") print(f"{self.name} reached level {self.lvl}!") # The assignment as I gave it last lesson: # str, dex, and int are now accessed through self print(f"STR {self.str} +{new_str}", f"DEX {self.dex} +{new_dex}", f"INT {self.int} +{new_int}" ) print("====================") print() self.str += new_str self.dex += new_dex self.int += new_int def attack(self): # *atk unppacks the list # atk = [5, 12] # *atk = 5, 12. the brackets are # dropped leaving a sequence of # integers instead of a list # so instead of randint(attacker.atk[0], attacker.atk[1]) return randint(*self.atk) def take_damage(self, damage): self.hp = self.hp - damage # 0 is immutable so if we type # >= instead of = python will # give an error instead of # silently assigning health to zero if 0 >= self.hp: print(f"{self.name} has been slain") # attacker.xp += self.xp_reward # update_level(attacker) # input("-Press any Key to quit-") return self.xp_reward else: print(f"{self.name} takes {damage}!") return 0 def is_alive(self): return 0 < self.hp def combat(attacker, defender): xp_yield = defender.take_damage(attacker.attack()) if xp_yield: # 0 is False attacker.xp += xp_yield attacker.update_level() def game_loop(player, enemy): playing = True while playing: print('--------------------') cmd = input("Do you want to attack? yes/no: ").lower() if cmd in 'yes': combat(player, enemy) elif cmd in 'no': print(f"{enemy.name} takes the opportunity to attack!") combat(enemy, player) else: pass if False == enemy.is_alive(): playing = False if False == player.is_alive(): playing = False hero = character( name="hero", lvl=1, xp=0, lvl_next=25, str=1, dex=1, int=1, hp=30, atk=[5, 12], xp_reward=25, loot_reward=("gold", 2) ) imp = character( name="imp", lvl=1, xp=0, lvl_next=25, str=1, dex=1, int=1, hp=30, atk=[5, 12], xp_reward=25, loot_reward=("gold", 6) ) game_loop(hero, imp) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
char = {'lvl' : 1, 'xp' : 0, 'lvl_next' : 25, } stats = {'str' : 1, 'dex' : 1, 'int' : 1, } def update_level(char, stats): new_str, new_dex, new_int = 0, 0, 0 while char['xp'] >= char['lvl_next']: char['lvl'] += 1 char['xp'] = char['xp'] - char['lvl_next'] char['lvl_next'] = round(char['lvl_next'] * 1.5) new_str += 1 new_dex += 1 new_int += 1 print(f"level: {char['lvl']}") # The assignment as I gave it last lesson: print(f"STR {stats['str']} +{new_str}", f"DEX {stats['str']} +{new_dex}", f"INT {stats['int']} +{new_int}" ) stats['str'] += new_str stats['dex'] += new_dex stats['int'] += new_int char['xp'] += 25 update_level(char, stats) print('-----------') char['xp'] += 200 update_level(char, stats) print('-----------') char['xp'] += 200 update_level(char, stats) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | lvl = 1 xp = 0 lvl_next = 25 p_str = 0 p_dex = 0 p_int = 0 while xp >= lvl_next: lvl += 1 xp -= lvl_next lvl_next = round(lvl_next * 1.5) print("level:", lvl) print("to level: {}%".format(int((xp / lvl_next)*100))) print(f"Next: {lvl_next}") |
#Python 3 - Win10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# TEST UTILITY FUNCTIONS: def rand_familty(lst): return lst[rand.randrange(0, len(lst))] def sanitize_name(family): return family.replace(' ', '_') def get_validate_family(widget, fam, default): widget.tag_add('<f_test>', f"{tk.END} +1c") fam, default = sanitize_name(fam), sanitize_name(default) try: widget.tag_configure('<f_test>', font=f"{fam} 8") return fam except: widget.tag_configure('<f_test>', font=f"{default} 8") return default def valid_rand_family(widget, lst, default): return get_validate_family(widget, rand_familty(lst), default) # WRAP LINE FUNCTIONS: def wrapline_to_line(widget, search_index) -> [int]: line_length = text.count(f"{search_index} linestart", f"{search_index} lineend", "update", "chars", "xpixels" ) line_px_width = 0 line_end_index = [] text_area_width = text.winfo_width() wrap_first_index = search_index.split('.')[0] for char_col_index in range(0, line_length[0]+1): step_index = text.index(f"{wrap_first_index}.{char_col_index}") step_plus_1 = f"{step_index} +1c" char_px_width = text.count(step_index, step_plus_1, "update", "xpixels") if None == char_px_width: char_px_width = 0 line_px_width += char_px_width if 0 > char_px_width: line_end_index.append(char_col_index+1) line_px_width = 0 elif char_col_index == line_length[0]: line_end_index.append(char_col_index+1) return line_end_index def display_by_line(widget, search_index, wrap_lines_marks): line_num = search_index.split('.')[0] for index, wrap_point in enumerate(wrap_lines_marks): start = 0 if 0 == index else wrap_lines_marks[index-1] end = wrap_point print(text.get(f"{line_num}.{start}", f"{line_num}.{end}")) # TEST DATA SET: import tkinter as tk from tkinter import font as tkfont import random as rand root = tk.Tk() text = tk.Text(root, height=4) text.pack() font_fam = tkfont.families() font_range = [12,24] search_index = "3.4" fam_rand1 = valid_rand_family(text, font_fam, "Courier") fam_rand2 = valid_rand_family(text, font_fam, "Terminal") fam_rand3 = valid_rand_family(text, font_fam, "Times New Roman") text.insert('1.0', f"line 1 <{fam_rand1}>1234567890123456789012345678-01234567890123456789012345678901234567890123", "<ft_random1>") text.insert('1.0', f" line 2 <{fam_rand2}>1234567890123456789012345678-01234567890123456789012345678901234567890123", "<ft_random2>") text.insert('1.0', f" line 3 <{fam_rand3}>1234567890123456789012345678-01234567890123456789012345678901234567890123", "<ft_random3>") text.insert('1.0', f"\t Data Test Set\n", "<ft_2>") text.insert('1.0', f"\n", "<ft_1") text.tag_config("<ft_random1>", font=f'{fam_rand1} {rand.randrange(*font_range)}') text.tag_config("<ft_random2>", font=f'{fam_rand2} {rand.randrange(*font_range)}') text.tag_config("<ft_random3>", font=f'{fam_rand3} {rand.randrange(*font_range)}') text.update_idletasks() text.update() # TEST EXECUTION: wrap_lines_marks = wrapline_to_line(text, search_index) display_by_line(text, search_index,wrap_lines_marks) root.mainloop() |
#Python 3 - Win10
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
import tkinter as tk class Document(tk.Text): #@! Class Variables #================================================================================ children = [] old_insert = "1.0" carrot_moved = False #@! Constructor #================================================================================ def __init__(self, *args, **kewargs): super().__init__(*args, **kewargs) # print(f"<{self.__class__.__name__}> object created <{id(self)}> ") Document.children.append(id(self)) self.set_bindings() self.old_insert = "1.0" # Break bindings for special window hotkeys e.g. 'ctrl-i' self.tag_init() #@! Utility #================================================================================ def get_sel_index(self, event=None) -> (str, str): try: sel_first = self.index(f"{tk.SEL}.first") sel_last = self.index(f"{tk.SEL}.last") except: sel_first = self.index(f"{tk.INSERT}") sel_last = self.index(f"{tk.INSERT} +1c") return sel_first, sel_last #@! Tags #================================================================================ def tag_init(self): tag_list = { 'regular':{ '<b>' :{'foreground':'red'}, '<u>' :{'underline':1, 'underlinefg':'green'}, '<os>':{'overstrike':1, 'overstrikefg':'red'}, '<jl>':{'justify':'left'}, '<jc>':{'justify':'center'}, '<jr>':{'justify':'right'}, '<f>' :{'foreground':'red', 'font':'Helvetica 18 bold underline overstrike italic'}, }, } for tag in tag_list['regular']: self.tag_add(tag, "1.0", "1.2") self.tag_config(tag, tag_list['regular'][tag]) # Broken # print(self.tag_config('<f>')) # Step 2 get only the ranges that intersect the selection def tag_ranges_in_sel(self, tag:str, start:str="", end:str="") -> [int]: """Returns list of first index of intersection tag range""" # def overthinking_it(self, tag, start, end): # Q: What are we really after with all these functions? # A: 1 finding if there is a tag in the selected range # A: 2 finding if the full range IS tagged or NOT # A: 3 finding if part(s) of the selection range ARE tagged # Q: what built in tools are there to do that job? # A: tag_ranges() # Q: are they good enough? # A: maybe? # guard statement ranges = self.tag_ranges(tag) if () == ranges: return [] if start == "": start, end = self.get_sel_index() intersections = [] for index in range(0, len(ranges), 2): range_start = ranges[index] range_end = ranges[index+1] # Q: We want to know if; range does or does NOT intersect selection. if self.compare(range_start, "<", end) and self.compare(range_end, ">", start): intersections.append(index) # print("range is IN selection") else: pass # print("range is OUTside selection") return intersections # Step 3 determine if tag range encompasses the selection def tag_range_match_sel(self, tag:str, range_indices:[], start:str="", end:str="") -> bool: """Returns True if tag range matches selection""" # guard statement ranges = self.tag_ranges(tag) if () == ranges: return False if start == "": start, end = self.get_sel_index() if 1 == len(range_indices): index = range_indices[0] range_start = ranges[index] range_end = ranges[index+1] if self.compare(range_start, "<=", start) and self.compare(range_end, ">=", end): return True return False def check_tags(self, event=None): ranges = self.tag_ranges_in_sel('bold') self.tag_range_match_sel('bold', ranges) def bold(self, event=None) -> None: self.set_tag('<b>') def underline(self, event=None) -> None: self.set_tag('<u>') def overstrike(self, event=None) -> None: self.set_tag('<os>') def justify(self, event=None) -> None: self.set_tag('<jl>') def fon(self, event=None) -> None: self.set_tag('<f>') self.tag_raise('<f>') def set_tag(self, tag:str, event=None) -> None: sel_first, sel_last = self.get_sel_index() tag_detected = self.tag_ranges_in_sel(tag) part_tagged = self.tag_range_match_sel(tag, tag_detected) if not tag_detected or not part_tagged: self.tag_add(tag, sel_first, sel_last) else: self.tag_remove(tag, sel_first, sel_last) def carry_over(self, step): """Continues a style""" index = self.index(f"{tk.INSERT} -{step}c") char = self.get(index) while '\n' == char or ' ' == char: if "1.0" == index: return index = self.index(f"{index} -1c") char = self.get(index) tags = self.tag_names(index) for tag in tags: self.tag_add(tag, index, tk.INSERT) def valid_over(self, event, step): # ISSUE: update is not fast enough, style only continues if typing is slow ignore = ['space', 'Left', 'Right', 'Up', 'Down', 'Control_L', 'Control_R', 'End', 'Home', 'Prior', 'Next'] if event.keysym not in ignore: self.carry_over(step) #@! Status Info #================================================================================ def carrot_move(self, event=None): new_insert = self.index(tk.INSERT) if self.old_insert != new_insert: self.old_insert = new_insert carrot_moved = True print("carrot_move", new_insert) # update index on release else: carrot_moved = False #@! Inputs #================================================================================ def set_bindings(self): self.bind("<Key>", self.any_key_down) self.bind("<KeyRelease>", self.any_key_up) self.bind("<Control-b>", self.bold) self.bind("<Control-u>", self.underline) self.bind("<Control-f>", self.fon) self.bind("<Control-minus>", self.overstrike) self.bind("<Control-bracketright>", self.overstrike) def any_key_down(self, event=None): self.valid_over(event, 1) def any_key_up(self, event=None): self.carrot_move(event) self.valid_over(event, 2) root = tk.Tk() text = Document(root, height=4) text.pack() text.insert("1.0", "This is helvetica font\n", "font_hel") text.insert("1.0", "This is terminal font\n", "font_term") text.tag_config('font_hel', font='Helvetica 12') text.tag_config('font_term', font='Terminal 12') root.mainloop() |