bob_dole
06-13-2013, 03:39 AM
[SMART] Implementing MicroPaint (BotWatch™)
[Scroll to the bottom if you simply want to see the code. This is a learning community but I do not have any issues with people copy/pasting my code.]
What is MicroPaint (BotWatch™)
MicroPaint (BotWatch™) is a system similar to paint that allows you to monitor your bot's location and progress in a tiny window, without having your SMART window open. It includes the bot's name, a small snapshot of the bot in-game, and optionally some quick statistics regarding the bot.
BotWatch™ also includes a color border that will signal to you how the bot is doing. The colors go from Bright Neon Green (Just mined/chopped/looted/etc.) to green (Recently mined/chopped/looted) to yellow (been a little while since last ore mined, log chopped, or item looted) to Orange (Been a long time since last ore mined, log chopped, or item looted) to Red (It's been too long since the last loot, about to fail) to Bright Red (Script timed out, terminated, you were random'ed, or died).
BotWatch™ is especcially useful to those running several different bots as you can do something as awesome as this:
http://img203.imageshack.us/img203/6462/botwatch.png
One look at your screen and you can instantly check the status of all bots running Simba on your computer. The colored borders are an instant gauge of where your bots are at and what their status is. The top line is the nickname, the middle is a quick snapshot of your character, and the last three lines are optional statistics.
The Functions
As this tutorial is hopefully in Advanced Tutorials, a simple explanation of the functions and a quick example should be sufficient.
The following is the code used to determine what color the border will be.
function PercToCol (percent: integer): longint;
begin
if (percent <= 10) then
result := RGBtoColor (255 - (percent * 5), 0 ,0);
if (percent > 10) then
result := RGBtoColor (225 - (percent * 3), 0, 0);
if (percent > 30) then
result := RGBtoColor (255, 155 + ((percent - 35) * 3), 50 - percent);
if (percent > 45) then
result := RGBtoColor (255, 247 - (4 * (60 - percent)), percent);
if (percent > 60) then
result := RGBtoColor (20, 20 + (percent), 20);
if (percent > 75) then
result := RGBtoColor (20, 100 + (percent * (17 / 10)), 20);
if (percent >= 95) then
result := RGBtoColor (0, 255 ,0);
end;
PercToCol Color Outputs:
http://img692.imageshack.us/img692/3951/perctocol.png
Using PercToCol
While the PercToCol function converts a percentage into a color, how to determine that percentage is completely up the scripter to decide. For my woodcutting script my formula to determine color is:
if (lasttree < 0) then
lasttree := 0 ;
id := 100 - round(sqrt((LastTree)) / 6);
TheStatus := PercToCol(id);
You should mess around with the calculations and change them to be best fit depending on what you are doing, average time between successes, and any possible failures that could occur. LastTree is quite obviously the TimeFromMark of the last tree cut. It is then fed through my formula, converted into an id, and shoved into the color converter.
Neon Green - 0 to 900 milliseconds from last cut, basically when it has just cut down a tree.
Green - 900 milliseconds to 57.6 seconds
Orange/Yellow - 57.6 seconds to 176.4 seconds (~3 minutes)
Red/Dead - 176.4 seconds to 360 seconds (6 minutes)
The formula can easily be adjusted depending on what you are doing. If it is something takes a while, increase the time limit for each color. If it is something that should be done fast, decrease the time limit.
UpdateWatch(LastTree: integer)
UpdateWatch is the function that creates the actual bitmap and sends it to your SRL debug window. It is really the only function that matters. This function should be called right after a success, a Show-ending event such as random/death/out of supplies, and at the end of the script. The more often you can call this procedure, the better. Word for word here is the function as in my woodcutter. I have added comments for those that need a some help decrypting my code.
Procedure UpdateWatch(LastTree: integer); // UpdateWatch function, includes how long since the last logs were recieved.
var
bot, w, h, image, text, id: integer;
TheStatus: longint;
offset: TPoint;
PHour: string
begin
// Calculates the percentage based off of last time logs were received.
id := 100 - round(sqrt((LastTree)) / 6);
// Converts the percentage into a color. Function discussed above.
TheStatus := PercToCol(id);
// Offset from base cords that the client capture will be taken from.
offset := point (8, 24);
// Creating the bitmap to be worked with.
image := CreateBitmap (115,115);
// Taking the snapshot of the client.
bot := BitmapFromClient (200 + offset.x, 100 + offset.y, 300 + offset.x, 200 + offset.y);
// Drawing the colored border to the image.
RectangleBitmap(image, inttobox (0, 0, 114, 114), TheStatus);
// Draws the client snapshot on top of the colored border.
FastDrawTransparent(7, 7, bot, image);
// Draws the current player's name in the smallest size available.
text := BitmapFromText(Players[CurrentPlayer].Name, StatChars07);
// Measured the width and height of the name bitmap, in order to properly center.
GetBitmapSize(text, w, h);
// Sets the text's black background as transparent.
SetTransparentcolor (text, 0);
// Uses the width/height measured to properly center the players name.
FastDrawTransparent(round ((115 * 0.5) - (w / 2)), 12, text, image);
// Frees the bitmap. All the bitmaps we are making, they NEED to be free'd.
FreeBitmap(text);
// Prints the 'Time' label. Very similar to the above script.
text := BitmapFromText('Time: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 74, text, image)
FreeBitmap(text);
// Prints the time running. Timize can be found in my tutorial on paint. Similar to above.
text := BitmapFromText(timize(round (GetTimeRunning / 1000)), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 74, text, image);
FreeBitmap(text);
// Prints the 'Logs' label. Similar to above...
text := BitmapFromText('Logs: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 84, text, image)
FreeBitmap(text);
// Prints the amount of logs cut. Similar to above...
text := BitmapFromText(groupdigits (magics, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 84, text, image);
FreeBitmap(text);
// Prints the 'cash' label. Similar to above...
text := BitmapFromText('Cash: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14,94, text, image)
FreeBitmap(text);
// Prints the amount of cash made. Similar to above...
text := BitmapFromText(groupdigits (magics * price, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 94, text, image);
FreeBitmap(text);
// Loads and displays the MicroPaint.
DrawBitmapDebugImg(image);
DisplayDebugImgWindow(115, 115);
// Frees out bitmaps, an absolute must.
FreeBitmap(bot);
FreeBitmap(image);
end;
Final Product. (See BotWatch in action)
While I am sure most scriptwriters here already know how to implement this code into their script, I will write a simple program to show the final product for those who would rather copy and paste. I threw together a quick magic tree simulator. Copy and paste this code into your Simba to see what BotWatch can do.
program BotWatch;
{$DEFINE SMART8}
{$I SRL-OSR/SRL.Simba}
const
Player_Name = 'zezima'; // Player's name. In real script this would come from declareplayers.
Price = 1200; // The price of each log.
var
Logs_Cut, Last_Cut: integer;
function ClockReel(i: integer): string;
begin
if (i < 10) then
begin
result := '0' + inttostr(i);
Exit;
end;
result := inttostr(i);
end;
function Timize(Time: Integer): String;
var
seconds, minutes, hours: integer;
final: String;
begin
if (time < 60) then
begin
seconds := time;
Result := '0:00:' + clockReel(time);
Exit;
end;
hours := floor(time / 3600);
time := time - (hours * 3600);
minutes := floor(time / 60);
time := time - (minutes * 60);
seconds := time;
final := (inttostr(hours) + ':' + clockReel(minutes) + ':' + clockReel(seconds));
Result := final;
end;
function PercToCol (percent: integer): longint;
begin
if (percent <= 10) then
result := RGBtoColor (255 - (percent * 5), 0 ,0);
if (percent > 10) then
result := RGBtoColor (225 - (percent * 3), 0, 0);
if (percent > 30) then
result := RGBtoColor (255, 155 + ((percent - 35) * 3), 50 - percent);
if (percent > 45) then
result := RGBtoColor (255, 247 - (4 * (60 - percent)), percent);
if (percent > 60) then
result := RGBtoColor (20, 20 + (percent), 20);
if (percent > 75) then
result := RGBtoColor (20, 100 + (percent * (17 / 10)), 20);
if (percent >= 95) then
result := RGBtoColor (0, 255 ,0);
end;
Procedure UpdateWatch(LastTree: integer); // UpdateWatch function, includes how long since the last logs were recieved.
var
bot, w, h, image, text, id: integer;
TheStatus: longint;
offset: TPoint;
begin
// Calculates the percentage based off of last time logs were received.
id := 100 - round(sqrt((LastTree)) / 6);
// Converts the percentage into a color. Function discussed above.
TheStatus := PercToCol(id);
// Offset from base cords that the client capture will be taken from.
offset := point (8, 24);
// Creating the bitmap to be worked with.
image := CreateBitmap (115,115);
// Taking the snapshot of the client.
bot := BitmapFromClient (200 + offset.x, 100 + offset.y, 300 + offset.x, 200 + offset.y);
// Drawing the colored border to the image.
RectangleBitmap(image, inttobox (0, 0, 114, 114), TheStatus);
// Draws the client snapshot on top of the colored border.
FastDrawTransparent(7, 7, bot, image);
// Draws the current player's name in the smallest size available.
text := BitmapFromText(Player_Name, StatChars07);
// Measured the width and height of the name bitmap, in order to properly center.
GetBitmapSize(text, w, h);
// Sets the text's black background as transparent.
SetTransparentcolor (text, 0);
// Using our width and height we can center the players name.
FastDrawTransparent(round ((115 * 0.5) - (w / 2)), 12, text, image);
// Frees the bitmap. All the bitmaps we are making, they NEED to be free'd.
FreeBitmap(text);
// Prints the 'Time' label. Very similar to the above script.
text := BitmapFromText('Time: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 74, text, image)
FreeBitmap(text);
// Prints the time running. Timize can be found in my tutorial on paint. Similar to above.
text := BitmapFromText(timize(round (GetTimeRunning / 1000)), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 74, text, image);
FreeBitmap(text);
// Prints the 'Logs' label. Similar to above...
text := BitmapFromText('Logs: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 84, text, image)
FreeBitmap(text);
// Prints the amount of logs cut. Similar to above...
text := BitmapFromText(groupdigits (logs_cut, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 84, text, image);
FreeBitmap(text);
// Prints the 'cash' label. Similar to above...
text := BitmapFromText('Cash: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14,94, text, image)
FreeBitmap(text);
// Prints the amount of cash made. Similar to above...
text := BitmapFromText(groupdigits (logs_cut * price, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 94, text, image);
FreeBitmap(text);
// Loads and displays the MicroPaint.
DrawBitmapDebugImg(image);
DisplayDebugImgWindow(115, 115);
// Frees out bitmaps, an absolute must.
FreeBitmap(bot);
FreeBitmap(image);
end;
Procedure Kill;
begin
writeLn ('Script terminated, you must restart.');
UpdateWatch(360000);
end;
begin
setupSRL;
AddOnTerminate('kill');
MarkTime(Last_Cut);
Repeat
if (RandomRange (1,15) = 1) then
begin
Logs_cut := Logs_cut + 1;
writeln ('We recieved some logs.');
MarkTime(Last_Cut);
UpdateWatch (0);
end;
UpdateWatch (TimeFromMark(Last_Cut));
wait (randomrange(200,4000));
until (false);
end.
If you haven't already please check out my guide for Paint over in the intermediate section. Any comments, suggestions, questions are always welcome. If one person was helped by this tutorial the time spent on it has been worth it.
[Scroll to the bottom if you simply want to see the code. This is a learning community but I do not have any issues with people copy/pasting my code.]
What is MicroPaint (BotWatch™)
MicroPaint (BotWatch™) is a system similar to paint that allows you to monitor your bot's location and progress in a tiny window, without having your SMART window open. It includes the bot's name, a small snapshot of the bot in-game, and optionally some quick statistics regarding the bot.
BotWatch™ also includes a color border that will signal to you how the bot is doing. The colors go from Bright Neon Green (Just mined/chopped/looted/etc.) to green (Recently mined/chopped/looted) to yellow (been a little while since last ore mined, log chopped, or item looted) to Orange (Been a long time since last ore mined, log chopped, or item looted) to Red (It's been too long since the last loot, about to fail) to Bright Red (Script timed out, terminated, you were random'ed, or died).
BotWatch™ is especcially useful to those running several different bots as you can do something as awesome as this:
http://img203.imageshack.us/img203/6462/botwatch.png
One look at your screen and you can instantly check the status of all bots running Simba on your computer. The colored borders are an instant gauge of where your bots are at and what their status is. The top line is the nickname, the middle is a quick snapshot of your character, and the last three lines are optional statistics.
The Functions
As this tutorial is hopefully in Advanced Tutorials, a simple explanation of the functions and a quick example should be sufficient.
The following is the code used to determine what color the border will be.
function PercToCol (percent: integer): longint;
begin
if (percent <= 10) then
result := RGBtoColor (255 - (percent * 5), 0 ,0);
if (percent > 10) then
result := RGBtoColor (225 - (percent * 3), 0, 0);
if (percent > 30) then
result := RGBtoColor (255, 155 + ((percent - 35) * 3), 50 - percent);
if (percent > 45) then
result := RGBtoColor (255, 247 - (4 * (60 - percent)), percent);
if (percent > 60) then
result := RGBtoColor (20, 20 + (percent), 20);
if (percent > 75) then
result := RGBtoColor (20, 100 + (percent * (17 / 10)), 20);
if (percent >= 95) then
result := RGBtoColor (0, 255 ,0);
end;
PercToCol Color Outputs:
http://img692.imageshack.us/img692/3951/perctocol.png
Using PercToCol
While the PercToCol function converts a percentage into a color, how to determine that percentage is completely up the scripter to decide. For my woodcutting script my formula to determine color is:
if (lasttree < 0) then
lasttree := 0 ;
id := 100 - round(sqrt((LastTree)) / 6);
TheStatus := PercToCol(id);
You should mess around with the calculations and change them to be best fit depending on what you are doing, average time between successes, and any possible failures that could occur. LastTree is quite obviously the TimeFromMark of the last tree cut. It is then fed through my formula, converted into an id, and shoved into the color converter.
Neon Green - 0 to 900 milliseconds from last cut, basically when it has just cut down a tree.
Green - 900 milliseconds to 57.6 seconds
Orange/Yellow - 57.6 seconds to 176.4 seconds (~3 minutes)
Red/Dead - 176.4 seconds to 360 seconds (6 minutes)
The formula can easily be adjusted depending on what you are doing. If it is something takes a while, increase the time limit for each color. If it is something that should be done fast, decrease the time limit.
UpdateWatch(LastTree: integer)
UpdateWatch is the function that creates the actual bitmap and sends it to your SRL debug window. It is really the only function that matters. This function should be called right after a success, a Show-ending event such as random/death/out of supplies, and at the end of the script. The more often you can call this procedure, the better. Word for word here is the function as in my woodcutter. I have added comments for those that need a some help decrypting my code.
Procedure UpdateWatch(LastTree: integer); // UpdateWatch function, includes how long since the last logs were recieved.
var
bot, w, h, image, text, id: integer;
TheStatus: longint;
offset: TPoint;
PHour: string
begin
// Calculates the percentage based off of last time logs were received.
id := 100 - round(sqrt((LastTree)) / 6);
// Converts the percentage into a color. Function discussed above.
TheStatus := PercToCol(id);
// Offset from base cords that the client capture will be taken from.
offset := point (8, 24);
// Creating the bitmap to be worked with.
image := CreateBitmap (115,115);
// Taking the snapshot of the client.
bot := BitmapFromClient (200 + offset.x, 100 + offset.y, 300 + offset.x, 200 + offset.y);
// Drawing the colored border to the image.
RectangleBitmap(image, inttobox (0, 0, 114, 114), TheStatus);
// Draws the client snapshot on top of the colored border.
FastDrawTransparent(7, 7, bot, image);
// Draws the current player's name in the smallest size available.
text := BitmapFromText(Players[CurrentPlayer].Name, StatChars07);
// Measured the width and height of the name bitmap, in order to properly center.
GetBitmapSize(text, w, h);
// Sets the text's black background as transparent.
SetTransparentcolor (text, 0);
// Uses the width/height measured to properly center the players name.
FastDrawTransparent(round ((115 * 0.5) - (w / 2)), 12, text, image);
// Frees the bitmap. All the bitmaps we are making, they NEED to be free'd.
FreeBitmap(text);
// Prints the 'Time' label. Very similar to the above script.
text := BitmapFromText('Time: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 74, text, image)
FreeBitmap(text);
// Prints the time running. Timize can be found in my tutorial on paint. Similar to above.
text := BitmapFromText(timize(round (GetTimeRunning / 1000)), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 74, text, image);
FreeBitmap(text);
// Prints the 'Logs' label. Similar to above...
text := BitmapFromText('Logs: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 84, text, image)
FreeBitmap(text);
// Prints the amount of logs cut. Similar to above...
text := BitmapFromText(groupdigits (magics, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 84, text, image);
FreeBitmap(text);
// Prints the 'cash' label. Similar to above...
text := BitmapFromText('Cash: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14,94, text, image)
FreeBitmap(text);
// Prints the amount of cash made. Similar to above...
text := BitmapFromText(groupdigits (magics * price, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 94, text, image);
FreeBitmap(text);
// Loads and displays the MicroPaint.
DrawBitmapDebugImg(image);
DisplayDebugImgWindow(115, 115);
// Frees out bitmaps, an absolute must.
FreeBitmap(bot);
FreeBitmap(image);
end;
Final Product. (See BotWatch in action)
While I am sure most scriptwriters here already know how to implement this code into their script, I will write a simple program to show the final product for those who would rather copy and paste. I threw together a quick magic tree simulator. Copy and paste this code into your Simba to see what BotWatch can do.
program BotWatch;
{$DEFINE SMART8}
{$I SRL-OSR/SRL.Simba}
const
Player_Name = 'zezima'; // Player's name. In real script this would come from declareplayers.
Price = 1200; // The price of each log.
var
Logs_Cut, Last_Cut: integer;
function ClockReel(i: integer): string;
begin
if (i < 10) then
begin
result := '0' + inttostr(i);
Exit;
end;
result := inttostr(i);
end;
function Timize(Time: Integer): String;
var
seconds, minutes, hours: integer;
final: String;
begin
if (time < 60) then
begin
seconds := time;
Result := '0:00:' + clockReel(time);
Exit;
end;
hours := floor(time / 3600);
time := time - (hours * 3600);
minutes := floor(time / 60);
time := time - (minutes * 60);
seconds := time;
final := (inttostr(hours) + ':' + clockReel(minutes) + ':' + clockReel(seconds));
Result := final;
end;
function PercToCol (percent: integer): longint;
begin
if (percent <= 10) then
result := RGBtoColor (255 - (percent * 5), 0 ,0);
if (percent > 10) then
result := RGBtoColor (225 - (percent * 3), 0, 0);
if (percent > 30) then
result := RGBtoColor (255, 155 + ((percent - 35) * 3), 50 - percent);
if (percent > 45) then
result := RGBtoColor (255, 247 - (4 * (60 - percent)), percent);
if (percent > 60) then
result := RGBtoColor (20, 20 + (percent), 20);
if (percent > 75) then
result := RGBtoColor (20, 100 + (percent * (17 / 10)), 20);
if (percent >= 95) then
result := RGBtoColor (0, 255 ,0);
end;
Procedure UpdateWatch(LastTree: integer); // UpdateWatch function, includes how long since the last logs were recieved.
var
bot, w, h, image, text, id: integer;
TheStatus: longint;
offset: TPoint;
begin
// Calculates the percentage based off of last time logs were received.
id := 100 - round(sqrt((LastTree)) / 6);
// Converts the percentage into a color. Function discussed above.
TheStatus := PercToCol(id);
// Offset from base cords that the client capture will be taken from.
offset := point (8, 24);
// Creating the bitmap to be worked with.
image := CreateBitmap (115,115);
// Taking the snapshot of the client.
bot := BitmapFromClient (200 + offset.x, 100 + offset.y, 300 + offset.x, 200 + offset.y);
// Drawing the colored border to the image.
RectangleBitmap(image, inttobox (0, 0, 114, 114), TheStatus);
// Draws the client snapshot on top of the colored border.
FastDrawTransparent(7, 7, bot, image);
// Draws the current player's name in the smallest size available.
text := BitmapFromText(Player_Name, StatChars07);
// Measured the width and height of the name bitmap, in order to properly center.
GetBitmapSize(text, w, h);
// Sets the text's black background as transparent.
SetTransparentcolor (text, 0);
// Using our width and height we can center the players name.
FastDrawTransparent(round ((115 * 0.5) - (w / 2)), 12, text, image);
// Frees the bitmap. All the bitmaps we are making, they NEED to be free'd.
FreeBitmap(text);
// Prints the 'Time' label. Very similar to the above script.
text := BitmapFromText('Time: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 74, text, image)
FreeBitmap(text);
// Prints the time running. Timize can be found in my tutorial on paint. Similar to above.
text := BitmapFromText(timize(round (GetTimeRunning / 1000)), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 74, text, image);
FreeBitmap(text);
// Prints the 'Logs' label. Similar to above...
text := BitmapFromText('Logs: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14, 84, text, image)
FreeBitmap(text);
// Prints the amount of logs cut. Similar to above...
text := BitmapFromText(groupdigits (logs_cut, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 84, text, image);
FreeBitmap(text);
// Prints the 'cash' label. Similar to above...
text := BitmapFromText('Cash: ', StatChars07);
SetTransparentcolor (text, 0);
FastDrawTransparent(14,94, text, image)
FreeBitmap(text);
// Prints the amount of cash made. Similar to above...
text := BitmapFromText(groupdigits (logs_cut * price, ','), StatChars07);
GetBitmapSize(text, w, h);
SetTransparentcolor (text, 0);
FastDrawTransparent(99 - w, 94, text, image);
FreeBitmap(text);
// Loads and displays the MicroPaint.
DrawBitmapDebugImg(image);
DisplayDebugImgWindow(115, 115);
// Frees out bitmaps, an absolute must.
FreeBitmap(bot);
FreeBitmap(image);
end;
Procedure Kill;
begin
writeLn ('Script terminated, you must restart.');
UpdateWatch(360000);
end;
begin
setupSRL;
AddOnTerminate('kill');
MarkTime(Last_Cut);
Repeat
if (RandomRange (1,15) = 1) then
begin
Logs_cut := Logs_cut + 1;
writeln ('We recieved some logs.');
MarkTime(Last_Cut);
UpdateWatch (0);
end;
UpdateWatch (TimeFromMark(Last_Cut));
wait (randomrange(200,4000));
until (false);
end.
If you haven't already please check out my guide for Paint over in the intermediate section. Any comments, suggestions, questions are always welcome. If one person was helped by this tutorial the time spent on it has been worth it.