Home

Sunday, January 30, 2022

Python 3+ Win 10 - RPG Systems - Simple Validate User Input System 1

#Python 3+ Win 10

RPG Systems

Simple Validate User Input System 1

 

Brief:


_______________________________________________________________

2202/02/13 02:55 24hr PST
!! Post Updated !!    Tutorial / How To video added below.
 
This was actually part of my "Simple Turn System" tutorial. But I realized it was substantial enough to be it's own post. The system is meant to granulate the process of taking in user input, and checking if that input is valid. This is broken into three different parts.

  1. The prompt with a list of options
    • The player needs some idea of what they can do in the game at any given moment. So we need a way to convey that information.
  2. The validation check
    • Whether it is a "yes/no" question, a list of menu actions, or items in an inventory. We need a way to check if what the player has entered in response to the prompt was in fact an option displayed to the player.
  3. The validation loop
    • Generally we want to ask the player what they want to do until we get an response that is allowed at the time.

 

Functionality:


_______________________________________________________________
 
 I need to test the code in a working project to see if it needs any adjustments. But for the text project everything works as intended.
 
  • Improvements:
    • N/A
  • Issues:
    • N/A

 

Demo:


_______________________________________________________________
 


How To:



 

Code Snippet:


_______________________________________________________________
 
  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()

 

Credits & Resources:

 
  • Code formatted via: http://hilite.me/ : Styling: Python, monokai
 

 

-END-



Wednesday, January 26, 2022

Python 3 Win 7 - RPG Systems - Simple Battle System 1


 # Python Win 7

RPG Systems

Simple Battle System 1

 

Brief:

_______________________________________________________________
A very short series made to help classmates in CS100 back in 2013. This is a revamped version with classes instead of dictionaries. And a few additions to the code.

 

Functionality :

_______________________________________________________________
Improvements:
  • N/A
Issues:
  • N/A

 

Demo:

_______________________________________________________________

Code snippet:

  _______________________________________________________________
  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)


Credits & Resources:

  • CS 100 - Tm R.
  • Code formatted via: http://hilite.me/ : Styling: Python, monokai

 

-END-



Python 3 Win 7 - RPG Systems - Simple Level System 2


 # Python Win 7

RPG Systems

Simple Level System 2

 

Brief:

_______________________________________________________________
A very short series made to help classmates in CS100 back in 2013.

 

Functionality :

_______________________________________________________________
Improvements:
  • N/A
Issues:
  • N/A

 

Demo:

_______________________________________________________________



Code snippet:

  _______________________________________________________________
 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)


Credits & Resources:

  • CS 100 - Tm R.
  • Code formatted via: http://hilite.me/ : Styling: Python, monokai

 

-END-

Python 3 Win 7 - RPG Systems - Simple Level System 1


 # Python Win 7

RPG Systems

Simple Level System 1

 

Brief:

_______________________________________________________________
A very short series made to help classmates in CS100 back in 2013.

 

Functionality :

_______________________________________________________________
Improvements:
  • N/A
Issues:
  • N/A

 

Demo:

_______________________________________________________________


Code snippet:

  _______________________________________________________________
 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}")


Credits & Resources:

  • CS 100 - Tm R.
  • Code formatted via: http://hilite.me/ : Styling: Python, monokai

 

-END-

Python 3 - Win10: Tkinter 2 - Word Processor - Text Widget - Line Wrapping - Find Wrap Position 1

#Python 3 - Win10

Tkinter Word Processor

Text Widget: Line Wrapping

Find Wrap Position 1

Brief:

_______________________________________________________________
 
! ! Update ! ! Post With New Code

It has always driven me crazy trying to work with wrapping lines in tkinter. I've spent a while thinking about it and searching for any solutions. But I have one here that works! As usual I have a test environment along with the solution.

My first attempt at solving this problem involved getting the closest index to the @x,y coordinates to the left and right of the inspected wrapline. I was able to get the first and last character of the display line consistently. The only problem was that if the line I was inspecting was not within the visible text area. Querying pixel to index positions was not possible. I needed a solution that was independent of the current view.

I had actually stumbled upon the solution accidentally. I figured at first that I would sum the width of each index in the line until it passed the edge of the Text widget and that would give me the wrap index -1. But that was not working as the line width never exceeded the widget's width. This was because it was being set back to zero at some point in the loop. While debugging I found that the step before the summed width was set to zero it was the inverse of the current line width. After visually debugging the text window I found that the point at which the sum width inverted was the wrap position of the line and the zero sum was a newline character. After I had that I could determine the end position of each wrap line by looking for that width reversal and storing the index of the character.

 

Functionality:

_______________________________________________________________
As far as I can tell it will work in any condition. Now my inner programmer doubts this very much. However, it does seem to be the case.

Improvements:
  • N/A
Issues:
  •  N/A

 

Demo:

_______________________________________________________________
 

 

Code Snippet: 

_______________________________________________________________
  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()


Credits and Resources:

  • Code formatted via: http://hilite.me/ : Styling: Python, monokai
  1. https://tcl.tk/man/tcl8.6/TkCmd/text.htm
  2. https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text-methods.html
  3. https://docs.python.org/3/library/tkinter.ttk.html
  • https://stackoverflow.com/

 

-END-

Tuesday, January 25, 2022

Python 3 - Win10: Tkinter 1 - Word Processor - Text Widget - Font Styling 1

#Python 3 - Win10

Tkinter Word Processor

Text Widget Font Styling 1

Brief:

_______________________________________________________________
This script is a slice of my word processor project. I'm trying to get styling to function and feel good, or at least something like Microsoft 365 Word.
 
It feels like I'm groping around in the dark to complete the project. The built-in tkinter functions don't seem to be of use for acquired and affecting the data I want. Which I think means I don't understand how they are meant to be employed. The workings of tkinter are also a bit of a black box for me, which is frustrating to say the least.

 

Functionality:

_______________________________________________________________
Improvements:
  • N/A
Issues:
  • Continuation of previous tags
    • While typing quickly continuation of previous tags is lost
    • New text is briefly displayed as default before previous tags are applied
    • Note: this is not the case for text inserted within the existing domain of the tag

 

Demo:

_______________________________________________________________
Functionality Test:
  • General tag handling
    1. Tag / untag a range
    2. Fully tag a partially tagged range
    3. Untag a fully tagged range
  • Continue text
    • Get previous adjacent tags
    • If no adjacent text, step back through spaces and newlines until text is found
  • Bold text
  • Underline text
  • Overstrike text
  • Change font
    • Revert to previous font


 

 

Code Snippet: 

_______________________________________________________________ 
  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()


Credits and Resources:

Code formatted via: http://hilite.me/ : Styling: Python, monokai
 

-END-