Programming for the Absolute Beginner 2nd Edition by Jerry Ford
I was excited to see that a second edition of Jerry Ford's programming book was recently released. It uses Just BASIC (all examples also compatible with Liberty BASIC) to teach the fundamentals of programming.
Jump to Amazon's link
Here is the table of contents. :-)
1 Introduction to Programming
2 Creating Programs with Just BASIC
3 Creating Graphical User Interfaces
4 Working with Variables and Arrays
5 Making Decisions with Conditional Logic
6 Using Loops to Process Data
7 Improving Program Organization with Functions and Subroutines
8 Working with Text Files
9 Working with Sound and Graphics
10 Arcade-Style Computer Game Development
11 Debugging Your Applications
Monday, August 22, 2016
Friday, August 19, 2016
Graphing Data - Setting the stage
If I have a set of data for sales, or temperature, or mosquito populations, or anything at all and I want to plot it on an X/Y graph, what are my choices for layout?
Should our graphing library figure out what the ranges are and set its own scaling? Should it require that we tell it up front what the X and Y scales are? Maybe it can even be designed to allow for changing the scale on the fly?
It would be great if our data plotting graphics library could handle plotting to screen, saving that to a file as a bitmap, and also plotting to a printer.
It would be great if our data plotting graphics library could handle plotting to screen, saving that to a file as a bitmap, and also plotting to a printer.
How about data sources and formats? Should we design it to accept one or more strings of delimited values? In the graphing example at runbasic.com it allows you to upload a CSV file with data to plot. Maybe as an interesting example I should find a downloadable file from a scientific website and the project should be to plot a graph from that?
Feedback welcome. More to come.
Feedback welcome. More to come.
Labels:
bitmap,
climate data,
comma delimited,
csv,
data format,
format,
graphing,
layout,
library,
plotting,
printer,
printing,
sales,
scientific data
Monday, August 08, 2016
Graphing data
I get a lot of questions about how to draw graphs involving progressions of numbers, and comparisons of trends, etc. Liberty BASIC doesn't have a built in graphing function, but it does support rich graphics drawing.
So if you're looking for a way to draw graphs of money, web traffic, rainfall, or whatever else stay tuned over the next couple of weeks. We're going to blaze a trail!
There are at least a couple of options for solving this problem.
So if you're looking for a way to draw graphs of money, web traffic, rainfall, or whatever else stay tuned over the next couple of weeks. We're going to blaze a trail!
There are at least a couple of options for solving this problem.
- Export data out to a CSV file and use Excel to plot graphs
- Use Liberty BASIC's graphics drawing features and draw the graphs ourselves
We are going to draw the graphs ourselves.
Impatient for the next post? Check out this link!
Stay tuned!
Labels:
bar graphs,
basic programming,
cvs,
drawing,
excel,
graphics,
graphing,
liberty basic,
line graphs,
microsoft excel,
qbasic
Tuesday, August 02, 2016
Blast from the past - Whatever happened to PEEK and POKE?
I am reposting the following blog entry from ten years ago because it has been one of the most popular posts ever according to Blogger's statistics graph. The link at the end of the post was broken, so I updated it.
Enjoy!
http://www.libertybasicuniversity.com/lbnews/nl108/index.htm
Enjoy!
Sometimes I'm asked how to PEEK and POKE using Liberty BASIC. The short answer to this is, you can't. The long answer is more complicated.
The original home computers (like the classic Commodore 64, TRS-80, and Apple II models) were designed to be completely open to their owners. Most of the different parts of the computer like the sound, graphics, keyboard and joystick (there were no mice back then) were controlled by mapping them to different memory locations. So, the built-in commands didn't do everything you need? It was common to control the computer's equipment directly by sticking values into the memory locations that control that equipment, and reading the status back out. This was done with POKE and PEEK. This was a lot of fun, and usually useful too.
Since these early machines didn't multitask it was pretty safe to steal control of things away from the BASIC interpreter (which was a much operating system as there was). Then computers started running Windows, the Mac OS, Linux, etc. and allowed more than one program to run at a time. PEEK and POKE became problematic because if one program decided to mess with the screen for example, it might interfere with what other programs need to do their thing. Not only that, but computers today all have different kinds of hardware in them, so even if you could PEEK and POKE the exact memory locations would be different from machine to machine. So that's the bad news. :-(
The good news is that we can still have a power trip. How? Your operating system manages all the hardware for you behind operating system functions. These functions make every computer look more or less the same to the programmer. You can use these functions to do a lot of powerful things and most languages have a way to use them. For example Liberty BASIC programmers can use the CALLDLL command to make Windows API calls, which are the operating system calls of the Windows operating system. These are more complicated in general (and you can fill a whole bookshelf with information about them) than the old fashioned PEEK and POKE, but at least we aren't left without some way to pull rank on our computers. ;-)
So if you're missing the power and coolness of PEEK and POKE, why not try your hand at API calls?
Here is a useful link to get you started:
The original home computers (like the classic Commodore 64, TRS-80, and Apple II models) were designed to be completely open to their owners. Most of the different parts of the computer like the sound, graphics, keyboard and joystick (there were no mice back then) were controlled by mapping them to different memory locations. So, the built-in commands didn't do everything you need? It was common to control the computer's equipment directly by sticking values into the memory locations that control that equipment, and reading the status back out. This was done with POKE and PEEK. This was a lot of fun, and usually useful too.
Since these early machines didn't multitask it was pretty safe to steal control of things away from the BASIC interpreter (which was a much operating system as there was). Then computers started running Windows, the Mac OS, Linux, etc. and allowed more than one program to run at a time. PEEK and POKE became problematic because if one program decided to mess with the screen for example, it might interfere with what other programs need to do their thing. Not only that, but computers today all have different kinds of hardware in them, so even if you could PEEK and POKE the exact memory locations would be different from machine to machine. So that's the bad news. :-(
The good news is that we can still have a power trip. How? Your operating system manages all the hardware for you behind operating system functions. These functions make every computer look more or less the same to the programmer. You can use these functions to do a lot of powerful things and most languages have a way to use them. For example Liberty BASIC programmers can use the CALLDLL command to make Windows API calls, which are the operating system calls of the Windows operating system. These are more complicated in general (and you can fill a whole bookshelf with information about them) than the old fashioned PEEK and POKE, but at least we aren't left without some way to pull rank on our computers. ;-)
So if you're missing the power and coolness of PEEK and POKE, why not try your hand at API calls?
Here is a useful link to get you started:
http://www.libertybasicuniversity.com/lbnews/nl108/index.htm
Labels:
api,
api calls,
apple ii,
basic,
calldll,
commodore 64,
gpf,
home computer,
liberty basic,
memory,
memory location,
memory protection,
peek,
poke,
qbasic,
trs-80
High DPI Issues
These days we see the arrival of very high resolution displays such as Apple's retina screens and the 4K monitors. These are very cool, but for programmers they can be a real pain in the side.
Any application designed for a normal monitor will be tiny when viewed on a very high resolution monitor. Windows provides a way to make things larger on screen so that the user isn't left reaching for a magnifying glass, but the effect isn't consistently applied to all the widgets and fonts, so that the user is left with an unsatisfying and unsatisfactory result.
The programming is left with the job of cleaning this up.
Check out the ongoing thread about this in our forum.
http://libertybasic.conforums.com/index.cgi?board=general&action=display&num=1456442864&start=0
Any application designed for a normal monitor will be tiny when viewed on a very high resolution monitor. Windows provides a way to make things larger on screen so that the user isn't left reaching for a magnifying glass, but the effect isn't consistently applied to all the widgets and fonts, so that the user is left with an unsatisfying and unsatisfactory result.
The programming is left with the job of cleaning this up.
Check out the ongoing thread about this in our forum.
http://libertybasic.conforums.com/index.cgi?board=general&action=display&num=1456442864&start=0
Labels:
4k monitor,
basic programming,
display,
dpi,
fonts,
gui,
high dpi,
liberty basic,
mac os,
problems,
programming,
retina,
scaling,
widgets,
windows
Dictionary lookup - bug fixes
One of our eagle-eyed forum members noticed a bug in our dictionary code (thanks to tsh!). In a couple of places the code that dealt with keys was not looking for the end of the key name. This caused problems when two keys started the same. For example let's say we have a key "name" and another key "names of next of kin". The second key starts with "name", and the code wasn't written to avoid a false detection of this sort.
The solution to this problem is to fully qualify the key names by adding the "~value~" tag into the detection code so that when we look for "name" we are actually looking for a match on "~key~name~value~" and not "~key~name", which would give us a false positive if we looking at any key that happened to start with "name"
Here is the fixed version of our dictionary inspector. Enjoy!
nomainwin
WindowWidth = 555
WindowHeight = 438
dim keys$(1000)
dim info$(10, 10)
global dictionary$, keyCount, lastKey$
call readDictionary
texteditor #main.value, 175, 7, 360, 365
listbox #main.keys, keys$(), [keySelected], 5, 7, 160, 365
menu #main, "Key", "New", [newKey], "Delete", [deleteKey]
menu #main, "Edit"
open "Dictionary inspector" for window as #main
#main "trapclose [quit]"
#main.keys "singleclickselect"
#main.value "!autoresize";
call loadKeys
[main.inputLoop] 'wait here for input event
wait
[newKey] 'ask the user for a new key
call saveValue
prompt "Enter a name for the key."; newKey$
if newKey$ <> "" then
call setValueByName newKey$, ""
call loadKeys
#main.keys "select "; newKey$
#main.value "!cls";
#main.value "!setfocus";
call collectGarbage
call writeDictionary
lastKey$ = newKey$
end if
wait
[deleteKey] 'left for later
notice "Delete not implemented yet."
wait
[keySelected] 'a key in the list was selected
call saveValue
#main.keys "selection? selectedKey$"
selectedValue$ = getValue$(selectedKey$)
#main.value "!contents selectedValue$";
lastKey$ = selectedKey$
wait
[quit] 'End the program
call saveValue
close #main
end
sub saveValue 'if the value is changed, save it
if lastKey$ <> "" then
#main.value "!modified? modified$";
if modified$ = "true" then
#main.value "!contents? saveThisValue$";
call setValueByName lastKey$, saveThisValue$
call collectGarbage
call writeDictionary
end if
end if
end sub
function getKeys$(delimiter$)
global keyCount
pointer = 1
while pointer <> 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
keyCount = keyCount + 1
end if
end if
wend
end function
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
sub readDictionary
if fileExists(DefaultDir$, "dictionary.dat") then
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end if
end sub
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + "~key~" + key$ + "~value~"
end if
end if
wend
dictionary$ = newDictionary$
end sub
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$+"~value~")
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
sub loadKeys
keyList$ = getKeys$("~")
redim keys$(keyCount)
for item = 1 to keyCount
keys$(item-1) = word$(keyList$, item, "~")
next item
#main.keys "reload"
end sub
function fileExists(path$, filename$) ' Does file exist?
files path$, filename$, info$(
fileExists = val(info$(0, 0)) > 0
end function
The solution to this problem is to fully qualify the key names by adding the "~value~" tag into the detection code so that when we look for "name" we are actually looking for a match on "~key~name~value~" and not "~key~name", which would give us a false positive if we looking at any key that happened to start with "name"
Here is the fixed version of our dictionary inspector. Enjoy!
nomainwin
WindowWidth = 555
WindowHeight = 438
dim keys$(1000)
dim info$(10, 10)
global dictionary$, keyCount, lastKey$
call readDictionary
texteditor #main.value, 175, 7, 360, 365
listbox #main.keys, keys$(), [keySelected], 5, 7, 160, 365
menu #main, "Key", "New", [newKey], "Delete", [deleteKey]
menu #main, "Edit"
open "Dictionary inspector" for window as #main
#main "trapclose [quit]"
#main.keys "singleclickselect"
#main.value "!autoresize";
call loadKeys
[main.inputLoop] 'wait here for input event
wait
[newKey] 'ask the user for a new key
call saveValue
prompt "Enter a name for the key."; newKey$
if newKey$ <> "" then
call setValueByName newKey$, ""
call loadKeys
#main.keys "select "; newKey$
#main.value "!cls";
#main.value "!setfocus";
call collectGarbage
call writeDictionary
lastKey$ = newKey$
end if
wait
[deleteKey] 'left for later
notice "Delete not implemented yet."
wait
[keySelected] 'a key in the list was selected
call saveValue
#main.keys "selection? selectedKey$"
selectedValue$ = getValue$(selectedKey$)
#main.value "!contents selectedValue$";
lastKey$ = selectedKey$
wait
[quit] 'End the program
call saveValue
close #main
end
sub saveValue 'if the value is changed, save it
if lastKey$ <> "" then
#main.value "!modified? modified$";
if modified$ = "true" then
#main.value "!contents? saveThisValue$";
call setValueByName lastKey$, saveThisValue$
call collectGarbage
call writeDictionary
end if
end if
end sub
function getKeys$(delimiter$)
global keyCount
pointer = 1
while pointer <> 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
keyCount = keyCount + 1
end if
end if
wend
end function
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
sub readDictionary
if fileExists(DefaultDir$, "dictionary.dat") then
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end if
end sub
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$ + "~value~") = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + "~key~" + key$ + "~value~"
end if
end if
wend
dictionary$ = newDictionary$
end sub
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$+"~value~")
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
sub loadKeys
keyList$ = getKeys$("~")
redim keys$(keyCount)
for item = 1 to keyCount
keys$(item-1) = word$(keyList$, item, "~")
next item
#main.keys "reload"
end sub
function fileExists(path$, filename$) ' Does file exist?
files path$, filename$, info$(
fileExists = val(info$(0, 0)) > 0
end function
Wednesday, July 20, 2016
A dictionary lookup mini application
Here is a simple demo application for our keyed lookup library in development. It opens a window with keys on the left, and a text editor on the right. You can add new keys and edit the values for those keys by changing text on the right. The values are saved to the file dictionary.dat.
We will enhance this code for a couple more blog entries before we move on to some other project. :-)
Enjoy!
nomainwin
WindowWidth = 555
WindowHeight = 438
dim keys$(1000)
dim info$(10, 10)
global dictionary$, keyCount, lastKey$
call readDictionary
texteditor #main.value, 175, 7, 360, 365
listbox #main.keys, keys$(), [keySelected], 5, 7, 160, 365
menu #main, "Key", "New", [newKey], "Delete", [deleteKey]
menu #main, "Edit"
open "Dictionary inspector" for window as #main
#main "trapclose [quit]"
#main.keys "singleclickselect"
#main.value "!autoresize";
call loadKeys
[main.inputLoop] 'wait here for input event
wait
[newKey] 'ask the user for a new key
call saveValue
prompt "Enter a name for the key."; newKey$
if newKey$ <> "" then
call setValueByName newKey$, ""
call loadKeys
#main.keys "select "; newKey$
#main.value "!cls";
#main.value "!setfocus";
call writeDictionary
lastKey$ = newKey$
end if
wait
[deleteKey] 'left for later
notice "Delete not implemented yet."
wait
[keySelected] 'a key in the list was selected
call saveValue
#main.keys "selection? selectedKey$"
selectedValue$ = getValue$(selectedKey$)
#main.value "!contents selectedValue$";
lastKey$ = selectedKey$
wait
[quit] 'End the program
call saveValue
close #main
end
sub saveValue 'if the value is changed, save it
if lastKey$ <> "" then
#main.value "!modified? modified$";
if modified$ = "true" then
#main.value "!contents? saveThisValue$";
call setValueByName lastKey$, saveThisValue$
call collectGarbage
call writeDictionary
end if
end if
end sub
function getKeys$(delimiter$)
global keyCount
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$) = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
keyCount = keyCount + 1
end if
end if
wend
end function
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
sub readDictionary
if fileExists(DefaultDir$, "dictionary.dat") then
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end if
end sub
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, key$) = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + key$
end if
end if
wend
dictionary$ = newDictionary$
end sub
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$)
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
sub loadKeys
keyList$ = getKeys$("~")
redim keys$(keyCount)
for item = 1 to keyCount
keys$(item-1) = word$(keyList$, item, "~")
next item
#main.keys "reload"
end sub
function fileExists(path$, filename$) ' Does file exist?
files path$, filename$, info$(
fileExists = val(info$(0, 0)) > 0
end function
We will enhance this code for a couple more blog entries before we move on to some other project. :-)
Enjoy!
nomainwin
WindowWidth = 555
WindowHeight = 438
dim keys$(1000)
dim info$(10, 10)
global dictionary$, keyCount, lastKey$
call readDictionary
texteditor #main.value, 175, 7, 360, 365
listbox #main.keys, keys$(), [keySelected], 5, 7, 160, 365
menu #main, "Key", "New", [newKey], "Delete", [deleteKey]
menu #main, "Edit"
open "Dictionary inspector" for window as #main
#main "trapclose [quit]"
#main.keys "singleclickselect"
#main.value "!autoresize";
call loadKeys
[main.inputLoop] 'wait here for input event
wait
[newKey] 'ask the user for a new key
call saveValue
prompt "Enter a name for the key."; newKey$
if newKey$ <> "" then
call setValueByName newKey$, ""
call loadKeys
#main.keys "select "; newKey$
#main.value "!cls";
#main.value "!setfocus";
call writeDictionary
lastKey$ = newKey$
end if
wait
[deleteKey] 'left for later
notice "Delete not implemented yet."
wait
[keySelected] 'a key in the list was selected
call saveValue
#main.keys "selection? selectedKey$"
selectedValue$ = getValue$(selectedKey$)
#main.value "!contents selectedValue$";
lastKey$ = selectedKey$
wait
[quit] 'End the program
call saveValue
close #main
end
sub saveValue 'if the value is changed, save it
if lastKey$ <> "" then
#main.value "!modified? modified$";
if modified$ = "true" then
#main.value "!contents? saveThisValue$";
call setValueByName lastKey$, saveThisValue$
call collectGarbage
call writeDictionary
end if
end if
end sub
function getKeys$(delimiter$)
global keyCount
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$) = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
keyCount = keyCount + 1
end if
end if
wend
end function
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
sub readDictionary
if fileExists(DefaultDir$, "dictionary.dat") then
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end if
end sub
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, key$) = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + key$
end if
end if
wend
dictionary$ = newDictionary$
end sub
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$)
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
sub loadKeys
keyList$ = getKeys$("~")
redim keys$(keyCount)
for item = 1 to keyCount
keys$(item-1) = word$(keyList$, item, "~")
next item
#main.keys "reload"
end sub
function fileExists(path$, filename$) ' Does file exist?
files path$, filename$, info$(
fileExists = val(info$(0, 0)) > 0
end function
Labels:
demo,
dictionary,
example,
hashtable,
inspector,
keyed lookup,
liberty basic,
named lookup,
properties file,
qbasic,
tips and tricks
Friday, July 15, 2016
Dictionary lookup - getting the keys
When you have an array you can simply loop through the contents to examine what's there, but when you have a dictionary you need to have the list of keys so that you can look up each value in the dictionary. For that, we need a getKeys$() function. The following function returns a single string with the keys from our global dictionary$ variable, each separated by a delimiter that we can specify.
function getKeys$(delimiter$)
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$) = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
end if
end if
wend
end function
Once have this string we can tease out each key. Here is an quick example that shows how to do this. The variable allKeys$ will hold all the keys, each separated by "~". Then we use the word$() function to get each key.
global dictionary$
call setValueByName "first", "Tom"
call setValueByName "last", "Thumb"
call setValueByName "phone", "555-555-1234"
allKeys$ = getKeys$("~")
print allKeys$
key = 1
while word$(allKeys$, key, "~") <> ""
key$ = word$(allKeys$, key, "~")
print "Key number "; key; " is "; key$
print " value = "; getValue$(key$)
key = key + 1
wend
Here is what the resulting output looks.
phone~last~first~
Key number 1 is phone
value = 555-555-1234
Key number 2 is last
value = Thumb
Key number 3 is first
value = Tom
Notice that the keys do not come out in the order that we put them in. This is typical in dictionary style lookup mechanisms. The ordering of keys is not guaranteed.
function getKeys$(delimiter$)
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, "~key~" + key$) = 0 then
getKeys$ = getKeys$ + key$ + delimiter$
keyList$ = keyList$ + "~key~" + key$
end if
end if
wend
end function
Once have this string we can tease out each key. Here is an quick example that shows how to do this. The variable allKeys$ will hold all the keys, each separated by "~". Then we use the word$() function to get each key.
global dictionary$
call setValueByName "first", "Tom"
call setValueByName "last", "Thumb"
call setValueByName "phone", "555-555-1234"
allKeys$ = getKeys$("~")
print allKeys$
key = 1
while word$(allKeys$, key, "~") <> ""
key$ = word$(allKeys$, key, "~")
print "Key number "; key; " is "; key$
print " value = "; getValue$(key$)
key = key + 1
wend
Here is what the resulting output looks.
phone~last~first~
Key number 1 is phone
value = 555-555-1234
Key number 2 is last
value = Thumb
Key number 3 is first
value = Tom
Notice that the keys do not come out in the order that we put them in. This is typical in dictionary style lookup mechanisms. The ordering of keys is not guaranteed.
Labels:
arrays,
basic,
dictionary,
hashmap,
keyed lookup,
keys,
liberty basic,
lookup by name,
named lookup,
qbasic,
tips and tricks
Wednesday, July 13, 2016
Dictionary lookup - saving to disk
One advantage of using our single string dictionary lookup technique is that saving to and reading from a disk file is amazingly simple. Just open the file and write the string.
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
Reading on the other hand requires a slightly more sophisticated technique. For example, if any of the keys or values have return characters in them then we want to make sure we read the whole file all the way to the end. For this we will use the input$() function.
sub readDictionary
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end sub
The ability to preserve return characters is useful more for the values than for the keys, which for most applications will probably just be short one or two word names.
sub writeDictionary
open "dictionary.dat" for output as #writeDict
print #writeDict, dictionary$
close #writeDict
end sub
Reading on the other hand requires a slightly more sophisticated technique. For example, if any of the keys or values have return characters in them then we want to make sure we read the whole file all the way to the end. For this we will use the input$() function.
sub readDictionary
open "dictionary.dat" for input as #readDict
length = lof(#readDict)
dictionary$ = input$(#readDict, length)
close #readDict
end sub
The ability to preserve return characters is useful more for the values than for the keys, which for most applications will probably just be short one or two word names.
Labels:
data storage,
dictionary,
disk file,
key,
keyed lookup,
liberty basic,
persistence,
read,
value,
write
Tuesday, July 12, 2016
Liberty BASIC on the Rosetta Code site
One of the members of our community forum posted about some graphics code that he wrote on the Rosetta Code site.
Here is his post.
http://libertybasic.conforums.com/index.cgi?board=LB3&action=display&num=1468265910
I'm glad he posted about it just as a reminder to what a great resource the Rosetta Code site is. There are many, many code samples there to learn from. Check out this link.
http://rosettacode.org/wiki/Category:Liberty_BASIC
And, if you'd like to get in on the action there are many Rosetta Code examples which have not yet been implemented in Liberty BASIC. For a list try this link:
http://rosettacode.org/wiki/Reports:Tasks_not_implemented_in_Liberty_BASIC
These are implemented in other language on the Rosetta Code site so if you'd like to try your hand at writing one or more in Liberty BASIC there are examples to glean inspiration from.
Here is his post.
http://libertybasic.conforums.com/index.cgi?board=LB3&action=display&num=1468265910
I'm glad he posted about it just as a reminder to what a great resource the Rosetta Code site is. There are many, many code samples there to learn from. Check out this link.
http://rosettacode.org/wiki/Category:Liberty_BASIC
And, if you'd like to get in on the action there are many Rosetta Code examples which have not yet been implemented in Liberty BASIC. For a list try this link:
http://rosettacode.org/wiki/Reports:Tasks_not_implemented_in_Liberty_BASIC
These are implemented in other language on the Rosetta Code site so if you'd like to try your hand at writing one or more in Liberty BASIC there are examples to glean inspiration from.
Labels:
basic,
examples,
fun projects,
learn,
liberty basic,
programming,
qbasic,
rosetta code,
tasks
Monday, July 11, 2016
Dictionary lookup - Garbage collection
If we need to use our keyed dictionary lookup functions for a purpose where we will change the values for any or all keys the string we save in dictionary$ will get larger each time we set a key and value. This is because the setValueByName subroutine sets a key and value by adding onto the front of the dictionary$ variable but it does not remove any preexisting value for that key. So if for example I set a key of "storeFolder" and a value of "c:\myStoreFolder" and then later I change the value to "c:\myOtherFolder" I will have two different entries for the key "storeFolder". Only the latest value will be returned by the getValue$() function.
So, how do we fix this? We implement a garbage collector. We can create a subroutine that makes a copy of dictionary$ that only has the latest value for each key.
Here is a first stab at a garbage collector subroutine.
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, key$) = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + key$
end if
end if
wend
dictionary$ = newDictionary$
end sub
So, how do we fix this? We implement a garbage collector. We can create a subroutine that makes a copy of dictionary$ that only has the latest value for each key.
Here is a first stab at a garbage collector subroutine.
sub collectGarbage
pointer = 1
while pointer > 0
'get the next key
pointer = instr(dictionary$, "~key~", pointer)
if pointer then
keyPointer = pointer + 5
pointer = instr(dictionary$, "~value~", pointer)
key$ = mid$(dictionary$, keyPointer, pointer - keyPointer)
if instr(keyList$, key$) = 0 then
value$ = getValue$(key$)
newDictionary$ = "~key~" + key$ + "~value~" + value$ + newDictionary$
keyList$ = keyList$ + key$
end if
end if
wend
dictionary$ = newDictionary$
end sub
Friday, July 08, 2016
Keyed dictionary lookup in Liberty BASIC
Liberty BASIC has a way to manage collections of data by using arrays and you look up the information by numeric position. You can do a lot with this but it doesn't let you look up information by name.
We can provide an easy to use way to do this in Liberty BASIC by using the string functions of Liberty BASIC. By using a single string we can have easy lookup of values by name and also have the ability to store the collection of values in a file and retrieve it simply. In some versions of BASIC this is only useful for small lists of information because of string size limitations of 255. Liberty BASIC permits strings of millions of characters so this is not a problem.
Here is a very simple demo of the concept just to get us started. In future postings we will explain and enhance the way this works.
global dictionary$
call setValueByName "first", "Tom"
call setValueByName "last", "Thumb"
call setValueByName "phone", "555-555-1234"
print getValue$("last")
print getValue$("blah")
print getValue$("phone")
print getValue$("first")
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$)
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
We can provide an easy to use way to do this in Liberty BASIC by using the string functions of Liberty BASIC. By using a single string we can have easy lookup of values by name and also have the ability to store the collection of values in a file and retrieve it simply. In some versions of BASIC this is only useful for small lists of information because of string size limitations of 255. Liberty BASIC permits strings of millions of characters so this is not a problem.
Here is a very simple demo of the concept just to get us started. In future postings we will explain and enhance the way this works.
global dictionary$
call setValueByName "first", "Tom"
call setValueByName "last", "Thumb"
call setValueByName "phone", "555-555-1234"
print getValue$("last")
print getValue$("blah")
print getValue$("phone")
print getValue$("first")
sub setValueByName key$, value$
dictionary$ = "~key~"+key$+"~value~"+value$+dictionary$
end sub
function getValue$(key$)
getValue$ = chr$(0)
keyPosition = instr(dictionary$, "~key~"+key$)
if keyPosition > 0 then
keyPosition = keyPosition + 5 'skip over key tag
valuePosition = instr(dictionary$, "~value~", keyPosition)
if valuePosition > 0 then
valuePosition = valuePosition + 7 'skip over value tag
endPosition = instr(dictionary$, "~key~", valuePosition)
if endPosition > 0 then
getValue$ = mid$(dictionary$, valuePosition, endPosition - valuePosition)
else
getValue$ = mid$(dictionary$, valuePosition)
end if
end if
end if
end function
Friday, July 01, 2016
Leveraging the lesson browser
Liberty BASIC has a cool feature that many people don't take advantage of. It's called the lesson browser. It allows you to create a collection of different programs in a single file arranged in an outline fashion along with comments for each program.
This is great for:
This is great for:
- Creating lessons (yeah)
- Sharing ideas with others
- Grouping related programs in a project
- Tracking the evolution of a program (a kind of versioning)
Here is a screenshot of the lesson browser in action. It is used to provide a tutorial and also example of new features of Liberty BASIC, but it can be used by users to create any collection of programs along with documentation.
Thursday, June 30, 2016
GETCLIENTRECT - Gettting the dimensions of the inside of a window
Sometimes it comes in really handy to know exactly what the limits of the inside of a window are. For example, if your Liberty BASIC program opens a window that is 500x400 pixels and you want to draw graphics that fit precisely or you are creating a game.. Think of Space Invaders where the aliens march back and forth on the screen! You need to know the precise inner dimensions of the window so you need to know how thick the window frame is and also the title bar on top of the window.
The trouble is, these measurements are not going to be the same from one version of Windows to another, and they change if you modify your system font sizes.
So what to do? Windows provides us a way to get the dimensions of the inside of the window by using the GETWINRECT function.
WindowWidth = 500
WindowHeight = 400
open "get client rectangle" for window as #w
'Get the window handle
hndl = hwnd(#w)
'Declare the struct which will be used to get the window client rectangle
struct winRect, orgX as long, orgY as long, cornerX as long, cornerY as long
'Make the GetClientRect call
calldll #user32, "GetClientRect", hndl as ulong, winRect as struct, result as Boolean
'Grab the width and height from the struct
wide=winRect.cornerX.struct-winRect.orgX.struct
high=winRect.cornerY.struct-winRect.orgY.struct
notice "inside of window frame width = "; wide; " height = "; high
The trouble is, these measurements are not going to be the same from one version of Windows to another, and they change if you modify your system font sizes.
So what to do? Windows provides us a way to get the dimensions of the inside of the window by using the GETWINRECT function.
WindowWidth = 500
WindowHeight = 400
open "get client rectangle" for window as #w
'Get the window handle
hndl = hwnd(#w)
'Declare the struct which will be used to get the window client rectangle
struct winRect, orgX as long, orgY as long, cornerX as long, cornerY as long
'Make the GetClientRect call
calldll #user32, "GetClientRect", hndl as ulong, winRect as struct, result as Boolean
'Grab the width and height from the struct
wide=winRect.cornerX.struct-winRect.orgX.struct
high=winRect.cornerY.struct-winRect.orgY.struct
notice "inside of window frame width = "; wide; " height = "; high
Tuesday, January 19, 2016
Copying a folder full of files in Liberty BASIC
Over on the Liberty BASIC forum at conforums.com one member was asking how to copy all the files in a folder for a database.
The hard way is to use the FILES statement and to write a bunch of code that tests and loops.
But is there an easier way?
One solution is to use the SHFileOperationA API call.
The hard way is to use the FILES statement and to write a bunch of code that tests and loops.
But is there an easier way?
One solution is to use the SHFileOperationA API call.
Chris Iverson shows how in his post. That and more in this thread. Click to read.
CallDLL #shell32, "SHFileOperationA", SHFILEOPSTRUCT as struct, CopyFolder as long
Labels:
api,
basic,
conforums,
copy,
copy files,
directory,
files,
liberty basic,
path,
SHFileOperationA,
win32
Friday, January 15, 2016
Date is between function in Liberty BASIC
Someone recently asked me if Liberty BASIC can answer the question about whether a date is between two other dates. Yes it can!
The following is my quick solution to his question.
answer = isDateBetween("12/21/2015", "12/15/2015", "12/30/2015")
if answer then print "yes" else print "no"
answer = isDateBetween("11/21/2015", "12/15/2015", "12/30/2015")
if answer then print "yes" else print "no"
function isDateBetween(aDate$, firstDate$, lastDate$)
aDays = date$(aDate$)
firstDays = date$(firstDate$)
lastDays = date$(lastDate$)
isDateBetween = firstDays < aDays and aDays < lastDays
end function
Enjoy!
The following is my quick solution to his question.
answer = isDateBetween("12/21/2015", "12/15/2015", "12/30/2015")
if answer then print "yes" else print "no"
answer = isDateBetween("11/21/2015", "12/15/2015", "12/30/2015")
if answer then print "yes" else print "no"
function isDateBetween(aDate$, firstDate$, lastDate$)
aDays = date$(aDate$)
firstDays = date$(firstDate$)
lastDays = date$(lastDate$)
isDateBetween = firstDays < aDays and aDays < lastDays
end function
Enjoy!
Labels:
basic,
basic programming,
between,
date,
function,
liberty basic
Thursday, January 14, 2016
Tiny BASIC part 5 - Adding color to PSET
It's much more fun to draw in color than with only a black pen, so let's add a third parameter to the PSET statement for color, for example:
The way this works is that it will look for a comma and a string expression after it sets PSETX and PSETY. If there is no comma it will skip over the part that gets a color parameter, so the default color will be black in that case.
The string will be the name of a valid Liberty BASIC color, for example red, blue, green, black, etc. The following code is stolen from the routine that parses for the PRINT statement. In a later post we will incorporate this into the PRINT code by calling it as a subroutine so that we won't have the same code twice.
So we call the [GetStringLiteral] subroutine and check E$ for an error. If there is none then we set PSETCOLOR$ to the value of B$ as shown here.
GOSUB [GetStringLiteral]
IF E$ <> "" THEN [Ready]
PSETCOLOR$ = B$
END IF
#GWIN "color "; PSETCOLOR$
Then we add a drawing command like so to set the color:
#GWIN "color "; PSETCOLOR$
Here is a sample that uses PSET with color!
And here is a screenshot!
PSET x, y, "color"To do this we need to add some code to our case "pset" block:
CASE "pset"
IF GWINOPEN = 0 THEN
E$ = "PSET error - Graphic window is not open"
GOTO [Ready]
END IF
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
PSETX = N
GOSUB [GetChar]
IF C$ <> "," THEN
E$= "Comma expected after x parameter"
GOTO [Ready]
END IF
C = C + 1
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
PSETY = N PSETCOLOR$ = "black"
GOSUB [GetChar]
IF C$ = "," THEN
C = C + 1
GOSUB [GetStringLiteral]
IF E$ <> "" THEN [Ready]
PSETCOLOR$ = B$
END IF
#GWIN "color "; PSETCOLOR$
#GWIN "down ; set "; PSETX; " "; PSETY
GOTO [FinishStatement]
The way this works is that it will look for a comma and a string expression after it sets PSETX and PSETY. If there is no comma it will skip over the part that gets a color parameter, so the default color will be black in that case.
The string will be the name of a valid Liberty BASIC color, for example red, blue, green, black, etc. The following code is stolen from the routine that parses for the PRINT statement. In a later post we will incorporate this into the PRINT code by calling it as a subroutine so that we won't have the same code twice.
[GetStringLiteral]
GOSUB [SkipSpace]
GOSUB [GetChar]
IF C$=G$ THEN
B$=""
[NextStringMember]
C = C + 1 : C$=MID$(A$,C,1)
IF C$="" THEN
E$="Unterminated string"
RETURN
ELSE
IF C$<>G$ THEN
B$=B$+C$
GOTO [NextStringMember]
END IF
END IF
C = C + 1 : C$=MID$(A$,C,1)
IF C$=G$ THEN
B$=B$+C$
GOTO [NextStringMember]
END IF
END IF
RETURN
So we call the [GetStringLiteral] subroutine and check E$ for an error. If there is none then we set PSETCOLOR$ to the value of B$ as shown here.
GOSUB [GetStringLiteral]
IF E$ <> "" THEN [Ready]
PSETCOLOR$ = B$
END IF
#GWIN "color "; PSETCOLOR$
Then we add a drawing command like so to set the color:
#GWIN "color "; PSETCOLOR$
Here is a sample that uses PSET with color!
5 graphicwin
10 pset x, y, "red"
20 pset x + 10, y, "blue"
30 pset x + 20, y, "green"
40 x = x + 1
50 y = y + 2
60 if x < 100 then goto 10
And here is a screenshot!
Labels:
basic,
color,
extending,
graphics,
interactive,
interpreter,
language implementation,
liberty basic,
open source,
parsing,
pset,
tiny basic
Wednesday, January 13, 2016
Liberty BASIC file type association
Liberty BASIC doesn't map the BAS file extension to itself when it is installed. This is because I didn't want to be so presumptuous as to steal a common file type away from another version of BASIC that might be installed.
So, because there is a bug in Windows which makes it really hard to remap a file extension and there have been discussions about this in the Liberty BASIC forum at conforums.com one of our member Chris Iverson (thanks Chris!) has contributed some Liberty BASIC code to solve this problem.
Click to see thread in the forum
Enjoy!
So, because there is a bug in Windows which makes it really hard to remap a file extension and there have been discussions about this in the Liberty BASIC forum at conforums.com one of our member Chris Iverson (thanks Chris!) has contributed some Liberty BASIC code to solve this problem.
Click to see thread in the forum
Enjoy!
Labels:
api,
bas,
basic,
basic programming,
bug,
conforums,
dll,
file type,
liberty basic,
registry,
user contribution,
win32,
windows
Sunday, January 10, 2016
Lunar lander revisited
Liberty BASIC comes with a nice introduction to video games called lander.bas.
- It's a good example of a timer driven game.
- It uses sprites which are actually generated on the fly using turtle graphics.
- It also shows proper technique of structured programming.
So I have some ideas that I am thinking about implementing to update it and make it an even more complete video game example:
- Add sound effects including ambient sounds, rocket motor noise and crash explosion.
- Animate the rocket motor so you can see rocket exhaust coming out.
- Add some flying space junk sprites that you need to avoid while trying to land.
- Add a colored starfield in the background.
Looking forward to this! Check back for updates!
Friday, January 08, 2016
Tiny BASIC part 4 - Adding a PSET statement
Okay, now we are ready to add a PSET statement for drawing pixels. Today we will simply add the ability to draw a single black pixel at a time. Next time we will add color!
Here is the code to accomplish this. This is just another SELECT CASE block to add after the one we added for the GRAPHICWIN command.
This was a bit tricky to write because there isn't really any documentation with the original Tiny BASIC source code, but by looking at the code for the other statements I think I figured it out correctly. It does seem to work.CASE "pset"
IF GWINOPEN = 0 THEN
E$ = "PSET error - Graphic window is not open"
GOTO [Ready]
END IF
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
PSETX = N
GOSUB [GetChar]
IF C$ <> "," THEN
E$= "Comma expected after x parameter"
GOTO [Ready]
END IF
C = C + 1
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
PSETY = N
#GWIN "down ; set "; PSETX; " "; PSETY
GOTO [FinishStatement]
The syntax for the new statement is PSET x, y
Let me explain what it does.
- First check to see if the graphic window is open. If it isn't then set E$ to be an error string. Then GOTO [Ready]. This will display the error.
- Then get the next expression using GOSUB [GetExpression]. This unleashes the expression parser which is easily the largest and most complex part of the Tiny BASIC source code. Then it checks for an error using IF E$<>"". If E$ does contain an error, then GOTO [Ready].
- Okay so got this far, so set PSETX to be what was in N, which is the result of the call to [GetExpression].
- Now get the next character, which we expect to be a comma to separate the x and y values. If the next character is not a comma, set E$ to be an error and GOTO [Ready].
- Now advance C one character by adding 1 to it. We do this because we found the expected comma, and now we need to skip over that so that we can get the next expression for our y value.
- Get the next expression using GOSUB [GetExpression]. Test E$ for an error and GOTO [Ready] if there is one.
- Get the value of N and put it into the variable PSETY.
- Finally, draw the pixel in the graphics window!
Here is the sample Tiny BASIC program that uses the PSET statement:
10 graphicwin
20 pset x, y
30 x = x + 1
40 y = y + 2
50 if x < 200 then goto 20
And here is the output of the program!
Thursday, January 07, 2016
Run BASIC Revisited - The easiest web development system on Earth
I got an inquiry yesterday about Run BASIC asking about what is special about it. The essential concept is that it is an all-in-one BASIC web application server. You can use it to create a dynamic web site, or to host in-house applications for your business or school, or use it to control your home. The possibilities are pretty much endless.
Here is a link to a white paper about Run BASIC.
http://www.libertybasic.com/RunBASICBreakthrough.pdf
Here is a link to the Run BASIC community forum.
http://runbasic.proboards.com/
Here is a link to a white paper about Run BASIC.
http://www.libertybasic.com/RunBASICBreakthrough.pdf
Here is a link to the Run BASIC community forum.
http://runbasic.proboards.com/
Wednesday, January 06, 2016
Tiny BASIC part 3 - Adding GRAPHICWIN statement
We are going to add GRAPHICWIN and PSET statements to Tiny BASIC.
Let's start with the really easy one. We will add a case "graphicwin" block to the end of the select case blocks that we examined in the last post. Here is what it looks like. The new code is in bold red.
CASE "let"So when you run Tiny BASIC and type the command graphicwin and press Enter it will open a small graphics window. When it does this it also sets the GWINOPEN flag to 1 to that we can check it if the program tries to open another graphics window. Only one graphics window will be allowed at a time.
GOSUB [GetLabel]
IF E$<>"" THEN [Ready]
CASE "graphicwin"
IF GWINOPEN = 1 THEN
PRINT "Graphics window is already open."
ELSE
GWINOPEN = 1
OPEN "Graphics" FOR graphics AS #GWIN
END IF
GOTO [FinishStatement]
END SELECT
The other thing that we want to do it close the graphics window and reset GWINOPEN to 0 if the program is started using the RUN statement.
So, we need to add the following code into the case "run" block. Here is how that code should look. The new code is in bold red.
CASE "run"So now the program will always start off in a clean state each time it is run!
IF GWINOPEN = 1 THEN
CLOSE #GWIN
GWINOPEN = 0
END IF
FOR I=27 TO 52 : A(I)=0 : NEXT I
L=27 : C=1
GOTO [FinishStatement2]
In our next post we will figure out how to add a PSET statement so we can draw some graphics!
Labels:
basic,
drawing,
graphics,
interpreter,
liberty basic,
parsing,
pset,
run,
tiny basic
Monday, January 04, 2016
Tiny BASIC part 2 - Adding new statements
This is part 2 of a series on extending tiny basic.bas which is an example that comes with Liberty BASIC v4.5.0.
In order to add some graphics capability I want to suggest two new commands just to start.
GRAPHICWIN width, height and PSET x, y, color$
These will enable us to open a window to draw in, and also to draw pixels of a specific color at a given x, y position. This will be a crude start, but it will be instructive because it will show the reader how to extend Tiny BASIC to do what is desired.
The first thing to do is figure out where in the code Tiny BASIC parses commands so we can add some more. The code below is the routine that does this. If you look carefully you will see a SELECT CASE statement and then a CASE statement for each Tiny BASIC command.
More later.
In order to add some graphics capability I want to suggest two new commands just to start.
GRAPHICWIN width, height and PSET x, y, color$
These will enable us to open a window to draw in, and also to draw pixels of a specific color at a given x, y position. This will be a crude start, but it will be instructive because it will show the reader how to extend Tiny BASIC to do what is desired.
The first thing to do is figure out where in the code Tiny BASIC parses commands so we can add some more. The code below is the routine that does this. If you look carefully you will see a SELECT CASE statement and then a CASE statement for each Tiny BASIC command.
More later.
[NextStatement]
GOSUB [GetLabel]
IF E$<>"" THEN [Ready]
SELECT CASE D$
CASE "if"
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
IF N<1 font="" then="">1>
B$=A$(L) : C=LEN(B$)+1
GOTO [FinishStatement]
END IF
GOSUB [GetLabel]
IF E$<>"" THEN [Ready]
IF D$<>"then" THEN
E$="'THEN' expected"
GOTO [Ready]
END IF
GOTO [NextStatement]
CASE "rem"
B$=A$(L) : C=LEN(B$)+1
GOTO [FinishStatement]
CASE "input"
GOSUB [GetVar]
IF E$<>"" THEN [Ready]
INPUT N : A(V)=N
GOTO [FinishStatement]
CASE "print"
[Print]
GOSUB [SkipSpace]
GOSUB [GetChar]
IF C$=G$ THEN
B$=""
[NextChar]
C = C + 1 : C$=MID$(A$,C,1)
IF C$="" THEN
E$="Unterminated string"
GOTO [Ready]
ELSE
IF C$<>G$ THEN
B$=B$+C$
GOTO [NextChar]
END IF
END IF
C = C + 1 : C$=MID$(A$,C,1)
IF C$=G$ THEN
B$=B$+C$
GOTO [NextChar]
END IF
PRINT B$;
ELSE
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
B=N1
IF B=N THEN
PRINT N;"*";
ELSE
PRINT N;
END IF
END IF
GOSUB [SkipSpace]
GOSUB [GetChar]
IF C$="," THEN C = C + 1 : GOTO [Print]
GOSUB [SkipSpace]
GOSUB [GetChar]
IF C$<>";" THEN
ELSE
C = C + 1
END IF
GOTO [FinishStatement]
CASE "clear"
FOR I=27 TO 52 : A(I)=0 : NEXT I
GOTO [FinishStatement]
CASE "run"
FOR I=27 TO 52 : A(I)=0 : NEXT I
L=27 : C=1
GOTO [FinishStatement2]
CASE "goto"
GOSUB [GetExpression]
IF E$<>"" THEN [Ready]
IF E>=N THEN L=27
C=1 : T=N
[NextGoto]
IF L=126 THEN
E$="Line not found"
GOTO [Ready]
END IF
GOSUB [GetNumber]
IF N=T THEN E=N : GOTO [NextStatement]
L = L + 1 : C=1
GOTO [NextGoto]
CASE "new"
FOR I=27 TO 125 : A$(I)="" : NEXT I
FOR I=27 TO 52 : A(I)=0 : NEXT I
IF E=0 THEN [FinishStatement]
GOTO [Ready]
CASE "cls"
CLS : GOTO [FinishStatement]
CASE "help"
FOR I=9 TO 18
B$=A$(I) : PRINT B$
NEXT I
GOTO [FinishStatement]
CASE "mem"
B=126
FOR I=27 TO 125
diffI = 152 - I 'Cheating here
B$=A$(diffI) : IF B$="" THEN B=diffI
NEXT I
B=126-B : PRINT B;"*";
PRINT " lines free"
GOTO [FinishStatement]
CASE "end"
GOTO [Ready]
CASE "bye"
GOTO [ExitTinyBAS]
CASE "list"
GOSUB [GetNumber] : T=N : A=L : I=C
IF T=0 THEN
GOSUB [GetLabel]
IF E$="" AND D$="pause" THEN I=C
E$=""
END IF
FOR L=27 TO 125
C=1 : GOSUB [GetNumber]
B=(T=0) OR (N=T)
IF B=TRUE THEN
IF A$<>"" THEN
PRINT A$
IF D$="pause" THEN
B = (L-26) mod 10
IF B=0 THEN PRINT "Pause..."; : INPUT AAA$
END IF
END IF
END IF
NEXT L
L=A : C=I
GOTO [FinishStatement]
CASE "save"
PRINT "SAVE, TBD"
CASE "load"
PRINT "LOAD, TBD"
CASE "let"
GOSUB [GetLabel]
IF E$<>"" THEN [Ready]
END SELECT
Labels:
basic,
basic programming,
color,
extend,
graphics,
liberty basic,
parsing,
pset,
source code,
tiny basic
Subscribe to:
Posts (Atom)