Home

Tuesday, February 22, 2022

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

#Python 3+ Win 10

Tkinter Word Processor

Text Widget Font Styling 2

 

Brief:

_______________________________________________________________
 
In the previous post here I could not figure out how to avoid the styling artifacts of color not updating quickly enough or the tag range being cut off while typing quickly.

I solved this issue with the code below.

in the original post I can remove carry_over, valid_over, in any_key_down replace the code with that of the code snippet.


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
import tkinter as tk
import re

class Document(tk.Text):
	def __init__(self, master, *args, **kewargs):
		super().__init__(*args, **kewargs)
		self.master = master
		self.master.bind("<KeyPress>", self.any_key_down)
		self.tag_add('<b>', "1.0", "1.2")
		self.tag_config('<b>', foreground='red')

	def any_key_down(self, event=None):
		print(event)
		char = re.findall(r"[a-zA-Z0-9\S ]", event.char)
		if 0 < len(char) and event.keysym not in ["BackSpace", "Escape"] or '\t' == event.char:
			insert = event.widget.index('insert-2c')
			tags = event.widget.tag_names(insert)
			for tag in tags:
				event.widget.tag_add(tag, 'insert-1c', 'insert')

root = tk.Tk()
text = Document(root, height=4)
text.pack()

text.insert("1.0", "This is helvetica font", "<b>")
text.insert("1.0", "This is terminal font", "font_term")
text.tag_config('<b>', font='Helvetica 12')
text.tag_config('font_term', font='Terminal 12')
text.insert("3.0", "This is terminal font\n", "font_term")

root.mainloop()

 

Credits & Resources:

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

 

-END-



Sunday, February 13, 2022

Update! 'How To' Video added - RPG Systems Simple Validate User Input System 1

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.


Monday, February 7, 2022

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

#Python 3 - Win10

Tkinter Word Processor

Text Widget: Line Wrapping

Find Wrap Position 2

Brief:

_______________________________________________________________
So as it turns out I didn't need to do any of that heavy lifting in my previous post. There were methods in place to do what I wanted. I found the answer in the tkinter/tcl docs while trying to figure out a different problem with the word processor application. I've been using this functions all the time but did not realize I could use the "display" sub-modifier when calling indices. But it's live and learn with coding.
 
The code is so minuscule it makes me want to cry for the time I took brute forcing the functionality.
 

 

Functionality:

_______________________________________________________________
...

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

 

Demo:

_______________________________________________________________
 N/A

 

Code Snippet: 

_______________________________________________________________

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

Credits and Resources:

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

 

-END-

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-