Number Guessing Game - Tcl/Tk

This is a Game Coding Tutorial

If you got here by sheer luck please read through the introduction at : https://andreiclinciu.net/?p=80&preview=true

The Game:
We will start with  a simple game where the player guesses a random number between 1 and 100  (or anyother maximum value) generated by the computer.
We'll start small and as we code we will add more functionalities along the way, this will make it easier to understand the process and changes.

You'll see that at every revision and version we will change specific things, and even change the way we did something in a previous version.
It's very important to understand that programming is not something static but a dynamic action of changes and improvements.

As we advance with our games we'll explain the changes less and less and we won't talk about previous concepts unless there is a catch.

In normal projects you need to think out the project on beforehand with all the requirements and then start coding. Requirements can change, and the code will change too so always review the old version with the newer version and see what we've added.

Version 1

Our first version has only 15 lines including drawing the screen and the logic of it all. Let's see how it works.
The first line includes the Graphical toolkit
We set the to guess  variable as a fixed number.
We then draw 3 things or widgets on the screen.
A label
An entry box and a button which tue user will click.
The grid command is called a Geometry manager.
It's the entity that draws the widget on the screen placing it at a predefined column and row location.
This will make it easier for us than calculating and specifying the location. If you want to add or remove widgets it becomes very easy
Since grid will manage everything for us.
We can join the 2 commands in 1 line like lines 6 and 7 or have them separate like lines 4 and 5.

Tk has 2 other geometry managers, pack and place which work differently depending on your needs.
We'll stick to  grid

Notice that the ttk::button function defines .btnGuess and specifies a command to use,
Then later we define the procedure which will read the current data  from the entry .txtGuess.
Then we use if to verify if it's the same as the variable numberToGuess.
The reason we use $::numberToGuess is that :: is the global namespace. Variables in Functions are local so if we used $numberToGuess it wouldn't work.
You can change it and test it out.
We'll see another way to access variables in the next version.

package require Tk

set numberToGuess 5
ttk::label .lblInfo -text "Guess a number between 1 and 10"
grid .lblInfo -row 1 -column 1
grid [ttk::entry .txtGuess ] -row 2 -column 1
grid [ttk::button .btnGuess -text "Guess!" -command guessTheNumber ] -row 3 -column 1
	
proc guessTheNumber {} {
	set currentGuess [.txtGuess get]
	puts "The current number is $currentGuess"
	if {$currentGuess == $::numberToGuess} {
		tk_messageBox -message "Congratulations, you've guessed it!"
	} else {
		tk_messageBox -message "That's not the number, try again"
	}
}

Version 2

On the 2d version we would like to do multiple things.
Use a random number each time.
See the total number of guesses the player has used.
Add another button that generates another number when clicked resetting the total guesses.

Cleanup a little bit.

For the random number generation we will use a predefined function called rnd. You can rename it to whatever you want.

We'll place all the other variables, labels, butons and grid functions in a initialize procedure which we will run at the end of the script.
You can play around and see what happens if the data within the initialize procedure where outside of a procedure and understand the logic.
We've added another label and button.

guessTheNumber
we define that we need the global variables   numberToGuess totalGuesses to be accesible within this function. This is the prefered way to access global data.
has now expanded to change the text of the label .lblInfo since it looks better.
You can always use a tk_messageBox if you prefer it that way.
We then show how many guesses he needed before he got the correct answer and we'll also disable the button if he already guessed it.

tryAnotherNumber
This function resets the totalGuesses and choses another random number.

package require Tk
proc initialize {} {
	global numberToGuess totalGuesses
	set numberToGuess [rnd 1 10]
	set totalGuesses 0
	ttk::label .lblInfo -text "Guess a number between 1 and 100"
	grid .lblInfo -row 1 -column 1
	grid [ttk::label .lblGuess -text "Guesses: $::totalGuesses"] -row 2 -column 1
	grid [ttk::entry .txtGuess ] -row 3 -column 1
	grid [ttk::button .btnGuess -text "Guess!" -command guessTheNumber ] -row 4 -column 1
	grid [ttk::button .btnAnother -text "Try another number!" -command tryAnotherNumber ] -row 4 -column 2
}

proc guessTheNumber {} {
	global numberToGuess totalGuesses
	set currentGuess [.txtGuess get]

	if {$currentGuess == $numberToGuess} {
		.lblInfo configure -text "Congratulations, you've guessed it!"
		.btnGuess configure -state disabled
	} else {
		incr totalGuesses
		.lblInfo configure -text "That's not the number, try again"
	}
	.lblGuess configure -text "Guesses: $totalGuesses"
}
proc tryAnotherNumber {} {
	global numberToGuess totalGuesses
	set totalGuesses 0
	set numberToGuess [rnd 1 10]
	.lblGuess configure -text "Guesses: $totalGuesses"
}
#Utilities
#Function that takes min and max and generates a random integer number
proc rnd {min max} {
		expr {int(($max - $min + 1) * rand()) + $min}
}

initialize

Version 3

For the 3d version we would like it if the computer would help us know how close we are to the number
We also want to make it more interesting so our numbers will be between 1 and  100.
Also limiting us to a maximum of 10 choices.

Take a moment to think how you could do this.

}

package require Tk
proc initialize {} {
	global numberToGuess totalGuesses
	set numberToGuess [rnd 1 100]
	set totalGuesses 0
	ttk::label .lblInfo -text "Guess a number between 1 and 100"
	grid .lblInfo -row 1 -column 1
	grid [ttk::label .lblGuess -text "Guesses: $::totalGuesses"] -row 2 -column 1
	grid [ttk::entry .txtGuess ] -row 3 -column 1
	grid [ttk::button .btnGuess -text "Guess!" -command guessTheNumber ] -row 4 -column 1
	grid [ttk::button .btnAnother -text "Try another number!" -command tryAnotherNumber ] -row 4 -column 2
}

proc guessTheNumber {} {
	global numberToGuess totalGuesses
	set currentGuess [.txtGuess get]

	if {$currentGuess == $numberToGuess} {
		.lblInfo configure -text "Congratulations, you've guessed it!"
		.btnGuess configure -state disabled
	} else {
		incr totalGuesses
		.lblInfo configure -text [howClose $currentGuess $numberToGuess]
	}
	.lblGuess configure -text "Guesses: $totalGuesses"
}
proc howClose {currentGuess numberToGuess} {
	set absDiff [expr {abs($numberToGuess - $currentGuess)}]
	if {$absDiff > 50} {
		set text "It's freezing outside and you've put the air conditioning on. That's how close you are."
	} elseif {$absDiff > 30} {
		set text "Nope: Frozen cold"
	}  elseif {$absDiff > 20} {
		set text "The sun is shining"
	} elseif {$absDiff > 10} {
		set text "You're very warm"
	} elseif {$absDiff > 5} { 
		set text "HOT"
	} else  {
		set text "The water's boiling "
	}
	
	
	return $text
}

proc tryAnotherNumber {} {
	global numberToGuess totalGuesses
	set totalGuesses 0
	set numberToGuess [rnd 1 100]
	.lblGuess configure -text "Guesses: $totalGuesses"
}
#Utilities
#Function that takes min and max and generates a random integer number
proc rnd {min max} {
		expr {int(($max - $min + 1) * rand()) + $min}
}

initialize

 

You might enjoy these similar articles:

Be the first to comment!

The comments section is closed.

Subscribe to my awesome newsletter!








What to expect: Ultimate Knowledge regarding Business Efficiency, Personalized Marketing Experience, Software Development and Cyber Security tips and tricks. 1-3 mails per month. Unsubscribe any time. See the privacy policy to learn how we take care of your information.