Blaise UK 112
Blaise UK 112
Blaise UK 112
ADVERTISING
Barnsten Delphi Products Page 72
Components for Developers Page 76
David Dirkse computer math/games in Pascal Page 25
Database Workbench Page 53
Help for Ukraine Page 75
Lazarus Handbook Pocket Page 11
Lazarus Handbook Pocket + Subscription Page 45
Lazarus Handbook PDF + Subscription Page 11
LIBRARY Internet Library Page 52
LIBRARY Lib Stick Page 20
Nexus DB 20 years Page 26
New subscription model Page 19
PDF Viewer 2023 Blaise Pascal Library USB stick Page 20
Subscription 2 year Page 73
Superpack 6 Items Page 74
Pascal is an imperative and procedural programming language, which Niklaus Wirth designed (left
below) in 1968–69 and published in 1970, as a small, efficient language intended to encourage good
programming practices using structured programming and data structuring. A derivative known as Object
Pascal designed for object-oriented programming was developed in 1985. The language name was chosen
Niklaus Wirth to honour the Mathematician, Inventor of the first calculator: Blaise Pascal (see top right).
Publisher: PRO PASCAL FOUNDATION in collaboration © Stichting Ondersteuning Programmeertaal Pascal
Editor - in - chief
Detlef D. Overbeek, Netherlands Tel.: Mobile: +31 (0)6 21.23.62.68
News and Press Releases email only to [email protected]
Subscriptions can be taken out online at www.blaisepascal.eu or by written order, or by sending an email to [email protected]
Subscriptions can start at any date. All issues published in the calendar year of the subscription will be sent as well.
Subscriptions run 365 days. Subscriptions will not be prolonged without notice. Receipt of payment will be sent by email.
Subscriptions can be paid by sending the payment to: ABN AMRO Bank Account no. 44 19 60 863 or by credit card or PayPal
Name: Pro Pascal Foundation (Stichting Ondersteuning Programeertaal Pascal)
IBAN: NL82 ABNA 0441960863 BIC ABNANL2A VAT no.: 81 42 54 147 (Stichting Ondersteuning Programmeertaal Pascal)
Subscription department Edelstenenbaan 21 / 3402 XA IJsselstein, Netherlands Mobile: + 31 (0) 6 21.23.62.68 [email protected]
Trademarks All trademarks used are acknowledged as the property of their respective owners.
Caveat Whilst we endeavor to ensure that what is published in the magazine is correct, we cannot
accept responsibility for any errors or omissions.
If you notice something which may be incorrect, please contact the Editor and we will publish a
correction where relevant.
Member of the Royal Dutch Library KONINKLIJKE BIBLIOTHEEK Member and donor of WIKIPEDIA
Subscriptions ( 2023 prices ) Internat. excl. VAT Internat. incl. 9% VAT Shipment TOTAL
Printed Issue (8 per year) ±60 pages : € 200 € 218 € 130 € 348
Electronic Download Issue (8 per year) ±60 pages : € 64,22 € 70
COPYRIGHT NOTICE
All material published in Blaise Pascal is copyright © SOPP Stichting Ondersteuning Programeertaal Pascal unless
otherwise noted and may not be copied, distributed or republished without written permission. Authors agree that code
associated with their articles will be made available to subscribers after publication by placing it on the website of the
PGG for download, and that articles and code will be placed on distributive data storage media. Use of program listings
by subscribers for research and study purposes is allowed, but not for commercial purposes. Commercial use of
program listings and code is prohibited without the written permission of the author.
At this moment we are very deep into developing Fresnel (The alternative for the LCL of
Lazarus) and Michael van Canneyt and Mattias Gärtner are working very hard on that.
We try to get that done by the time I will be in Backnang -Stuttgart / Heidelberg – area at
the 22scd-24th of September this year.
(https://www.blaisepascalmagazine.eu/lazarus-konferenz-2023-in-backnang-22-09-2023-24-09-2023/)
Mattias tries too build something for Skia and Michael will create the extra (mouse events)
for the library.
That is already an enormous step toward making Lazarus colourful.
Finally we want to do something that has no multiple OS compiler available:
Color setting as you wish – on whatever platform,
independent of the colour-scheme of the to be used OS.
Martin Friebe is continuing the very much enhanced Debugger for Lazarus. Please try, its
very interesting and helpful. There are about six or seven more articles to come so it is like
a course in debugging.
These new developments will be integrated into the next Lazarus Handbook as well.
Speaking about books: We will publish two new books:
1. Learning to program with FreePascal and Lazarus – it is a totally new written book
with a lot of simple lessons so that you will be able to create any program you want.
2. Creating Pas2Js Apps, in Delphi and in Lazarus.
We will write about details in the next edition of Blaise Pascal Magazine.
So ballistics is the study of the motion of projectiles, such as bullets, shells, and
rockets, in our script we deal only with balls. It is a branch of mechanics that deals with
the behavior of objects in motion. Ballistics can be divided into three main categories:
internal ballistics, external ballistics, and terminal ballistics.
So I translated the Delphi program with a few improvements into that script:
Of course we can simulate a cannonball using a physics simulation software or in our case integrate
that model with Pascal. This simulation allows you to blast a ball out of a cannon and challenge
yourself to hit a movable target. You can set parameters such as angle (elevation), initial speed
(powder charge), and mass (gravity), and explore the vector representations.
http://www.softwareschule.ch/examples/cannonball.txt
Blaise Pascal Magazine 112 2023 7
maXbox
Report C
maXbox Star ter annonball
Article Page 2 / 4
112 Simulation
m a Xb o x
Also an explainable
statistic is part of the script, as summary or detailed
(our standard case which hit the target):
Summary of study case
-----------------------------------------------------------------
Barrel Len 87, Angle 45.0, Initial V 24.0, gravity 1.0
Time in barrel 3.8 seconds
X distance at end of barrel 61.5
Y distance at end of barrel 61.5
Time to top of freeflight arc 15.1, 18.9 total
X distance to top of freeflight arc 226.5, 288.1 total
Height above barrel to top of freeflight arc 113.3, 174.8 total
Time to reach ground from max height 18.7, 37.6 total
X distance from top of freeflight arc to end 281.4, 569.5 total
The interesting thing is that this simulation shows how the motion of a projectile like a cannonball is
fundamentally the same as the orbit of a celestial body like the moon!
The rotate and translate routines developed are used here to elevate the cannon. The ball movement
loop is similar to a Bouncing Ball program with the addition of a horizontal component.
Initial velocities in the X and Y direction are proportional to the cosine and sine of the elevation angle
respectively.
The barrel is a bit tricky; We do assume that the cannonball inside the barrel is "rolling up a ramp" with
the component of gravity acting parallel to the barrel being the force acting to reduce the velocity of the
cannonball in both x and y directions, so we keep an eye on the distance function:
http://www.softwareschule.ch/examples/cannonball.txt
Blaise Pascal Magazine 112 2023 8
maXbox
Report C
maXbox Star ter annonball
Article Page 3/ 4
112 Simulation
m a Xb o x
function distance(p1,p2:TPoint):float;
begin
result:= sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y));
end;
Two Procedures, Rotate and Translate, do the rotation of points. Rotation about an
origin point of (0,0) is rather straightforward as we can see from the code below:
http://delphiforfun.org/Programs/bouncing_ball.htm
Blaise Pascal Magazine 112 2023 9
maXbox
Report C
maXbox Star ter annonball
Article Page 4 / 4
112 Simulation
m a Xb o x
In PyGame for example,
collision detection is done using Rect objects. The Rect object
offers various methods for detecting collisions between objects. Even the collision
between a rectangular and circular object such as a paddle and a ball can be detected
by a collision between two rectangular objects, the paddle and the bounding rectangle of
the ball. Now we can summarize the theoretic results in a procedure of our statistic:
{************* TheoreticalCalc **********}
procedure TheroreticalCalc;
var
root,T1, Vf, Vxf, Vyf, X1,Y1 : float;
TTop, Xtop, Ytop, Tlast, VyLast, Xlast, floor : float;
begin
with {stats.}amemo1.lines do begin
clear;
add(format('Barrel Len %d, Angle %6.1f, Initial V %6.1f, gravity %6.1f',
[barrellength,180*theta/pi,v1,g]));
if g = 0 then g := 0.001;
root := v1*v1 - 2*g*sin(theta)*Barrellength;
if root>=0 then begin
T1 :=(v1 - sqrt(root))/(g*sin(theta+0.001));
Vf := v1 - g*sin(theta)*T1;
Vxf :=Vf*cos(theta);
Vyf :=Vf*sin(theta);
X1 :=Barrellength*cos(theta);
Y1 :=Barrellength*sin(Theta);
floor:=(origin.y+ballradius)-groundlevel;
{out of barrel, Vx remains constant, Vy := Vyf- g*DeltaT}
{Vy=0 then Vyf-g*Ttop=0 or Ttop=Vyf/g}
Ttop:=Vyf/g;
{x distance at top} Xtop:=Vxf*Ttop;
{height at top = average y velocity+ time} Ytop:=(Vyf + 0)/2*TTop;
{Time to fall from ytop to groundlevel, descending part of projectiles path}
{speed when ball hits ground}
TLast:=sqrt(2*(Y1+YTop-floor)/g );
Xlast:=Vxf*TLast;
add(format('Time in barrel %6.1f seconds',[T1]));
add(format('X distance at end of barrel %6.1f',[X1]));
add(format('Y distance at end of barrel %6.1f',[Y1]));
add(format('Time to top of freeflight arc %6.1f, %6.1f total',[Ttop,T1+Ttop]));
add(format('X distance top of freeflight arc %6.1f, %6.1f total',[Xtop,X1+Xtop]));
add(format('Height above barrel to top of freeflight arc %6.1f, %6.1f total',
[Ytop,Y1+Ytop]));
add(format('Time to reach ground from max height %6.1f, %6.1f total',
[TLast,T1+Ttop+TLast]));
add(format('X distance from top of freeflight arc to end %6.1f, %6.1f total',
[XLast,X1+Xtop+XLast]));
end else add('Velocity too low, cannonball does not exit barrel');
end;
end;
By the way I asked ChatGPT how can I program cannonball in Pascal and the answer:
To program a cannonball in Pascal, you can use the following steps:
In this example code snippet, CircleRectCollision() is a custom function that detects collision between a
circle and a rectangle. You can modify this function to suit your needs; the main part of the script has only 4
procedures:
processmessagesOFF; loadStatForm(); loadmainForm(); UpdateImage();
https://en.wikipedia.org/wiki/Ballistics http://www.softwareschule.ch/examples/
cannonball64.txt
maXbox
ADVERTISEMENT
Price: € 75,00
LAZARUS HANDBOOK
POCKET + PDF AND
SUBSCRIPTION
ex Vat and Shipping
Starter Expert
INTRODUCTION:
The Picture below shows a HBot (H shaped robot).
These robots are widely used in gantries,(A Gantry Robot is an automated industrial system that
can also be referred to as a Cartesian Robot or a Linear Robot.) manufacturing such as SMD pick-
and-place, packaging and laser cutting. But also unexpected applications were found: T-shirt folding,
chess playing, tattoo machine. Advantages of this robot type are cost effectiveness, portability and ease
of control.
Figure 1: HBot
Figure 6: Actions
XpartialToBox; //area of mapC, B areas paintbox, these are the updated rectangles of mapX
procAWheelmovement; //place spokes on wheels in B areas.
procCwheelmovement; //add spoke to small wheels in C area.
procBeltMovement; // add purple dots on belt
For the last three procedures, the place of the spokes or dots is calculated from the position of the pen.
(0,0) is the center of area E.
The coordinates on E are (-320,-320) left top to (320,320) right bottom.
The procBeltMovement code is lengthy because the belt is divided into horizontal and vertical stretches and
also the arcs around the wheels.
Picture Right shows four paintboxes with added on-enter and on-
leave events. Code is provided by unit2. These paintboxes are
created at run time. Figure 9: The motor control
A left mouse button down on the top half of the L button moves the
left top motor counterclockwise. The bottom half causes clockwise
motion. These actions reverse for the right mouse button.
The R paintbox operates in a similar way, now causing clockwise
motion for a mouse button down on the top part.
The L+R buttons activate both motors moving either in the same or
in opposite directions.
Right pictured are the paintbox cells and right the direction
code is displayed.
Procedure procXpainting
is called which sets the moveflag and continuously calls
procedure procmove as long as the moveflag is set.
Procmove takes care of the speed. For single motor
operations, the time out period is doubled because the motor
has to turn twice the distance moving the pen in a diagonal
way.
Procmove calls procedure moveControl to calculate the
new pen position (penPosX,penPosY) , update mapX and
copy parts of mapX to paintbox1 on form1.
If outer bounds of the E area are reached procMove clears
moveFlag and pen motion stops. Figure 11: The paintbox cells
DEMO 3:
This demo plots some lines of text.
Text is preset in array demotext together with the coordinates of the first character,
the font height and of course the character string.
Procedure startdemo(3) calls procedure painttextline(line nr) for each
line of text .
Painttextline calls procedure drawdemochar(x,y) for each character of the line.
Drawdemochar finally calls movotoXY(x,y) and procmove(direction).
Remains to explain how the parameters for procedures movetoXY ( ) and
procmove( ) are calculated.
A bitmap called scanmap (width=60, height=60 pixels ) is erased for each
character with a black background. Then the character is painted in scanmap with
pencolor red and background white. The black color indicates the boundaries of
the character.
SCANMAP
Drawdemochar has variables scanX, scanY and x, y which point to pixel positions
of scanmap.
First scanX and scanY are set to 0;
Then function scanChar(var scanX,scanY) : Boolean;is called.
This function scans the scanmap (left to right, top to bottom) to find a red pixel and
return true after that red pixel is set to white.
ScanX,scanY point to the first red pixel found.
Next drawdemochar lowers the pen to set a dot. x is set to scanX , y is set to scanY
and function subscan(var x,y; var dir) : Boolean is called.
This function searches for neighbour red pixels of x,y.
If found, true is returned together with the direction code needed for the call to
procedure procmove( ) to step the pen.
Also x,y coordinates are updated to reflect the last red pixel position.
If subscan returns false, scanChar is called again to continue the search for remaining
red dots, starting at scanX,scanY.
The roman font is used to paint the characters in scanmap.
The GoDemoFlag must be true for textdrawing to continue.
A click on the stop button clears the flag, ending the demo.
https://www.blaisepascalmagazine.eu/product-overview/
USE WHERE EVER THE INTERNET IS AVAILABLE
Blaise Pascal Magazine 112 2023 8 2022
LIB-STICK ON USB CREDIT CARD
BLAISE PASCAL MAGAZINE
LIB-STICK USB-CARD: ALL ISSUES / CODE INCLUDED. SAME INTERFACE AS THE
INTERNET LIBRARY € 120,00
Starter Expert
INTRODUCTION
In secondary school students are
confronted with the theorem of Pythagoras:
In a right angled triangle, the square of the
hypothenuse c equals the sum of squares of the
right angled sides a and b.
Calculations with above formula usually result in roots,
numbers that only may be approximated.
Some values of a and b however result in an integer value of c,
such as
- 3,4 c=5
- 5,12 c=13
(3,4,5) and (5,12,13) are called Pythagorean triples.
The question arises: are there more triples?
This Delphi project was written to find all triples below 1000.
ANALOGOUS TRIPLES
(3,4,5) being a triple involves that also (6,8,10), (9,12,15) ..
are triples of similar triangles.
A simple criterion GCD(a,b) = 1 eliminates these
analogous triples
function GCD(a,b : word) : word; //greatest common divisor
var h : word;
begin
repeat
if a < b then
begin
h := a;
a := b;
b := h;
end;
a := a mod b;
until a = 0;
result := b;
end;
Time measurement.
A microseconds timer component is added to the project.
To find the real processing time without the burden of reporting (memo1.lines.add( string))
detected triples are first stored in array ptriples[ ]
type TP3 = record
a,b,c : word;
end;
…
var p3nr : byte; //sequence number of triple
ptriples : array[1..200] of TP3;
The project presented here has three selectable procedures to find Pythagorean triples.
METHOD NR 1.
Uses no floating point operations and a preset table of squares.
SUMMARY:
Var c2 : dword;
…
{a and b are incrementing variables in nested repeat..until loops}
C2 := squares[a] + squares[b];
C := b;
{a third nested repeat..until loop increments c}
If c2 = squares[c] then …. //report a,b,c as new triple
METHOD2
procedure p3method2;
var a,b : word;
a2,b2 : dword;
c : single;
begin
form1.proctimer.start;
for a := 1 to 998 do
begin
a2 := a*a;
for b := a+1 to 999 do
begin
b2 := b*b;
c := sqrt(a2 + b2);
if frac(c) = 0 then
if GCD(a,b) = 1 then addtriple(a,b,round(c));
end;
end;
form1.proctimer.stop;
end;
METHOD3
This method does not search for triples but uses formulas that yield triples.
Below is the theory:
https://www.blaisepascalmagazine.eu/product-category/books/
Starter Expert
� INTRODUCTION
I remember my first book about chess, when I was a kid. It was a book teaching young people how to
play chess. The first chapter started with a tale about children playing chess incorrectly:
they didn’t know the rules, so they put chess pieces randomly on the chessboard, and flicked them with
their fingers towards the other side. The wooden chess pieces flew in the air, bashed with each other.
Eventually most of the chess pieces fell off the chessboard onto the floor. The person with the last chess
piece remaining on the chessboard was the winner.
That was naturally a bad way to play chess. In the second chapter of the book, an adult came,
told children that they play chess wrong, and taught them the right way — how each figure moves,
how the king is special, what it means to check and then mate your opponent.
The book overall was great, and it’s likely responsible for my love for chess (the proper version of the
game, with rules instead of flicking objects) to this day.
That being said… Don’t you want to play some day this "incorrect" version of chess, the children’s version,
where nothing else matters except just sending each chess piece flying toward the other side?
In this series of articles we will go back in time, erase our hard-earned knowledge about how to really
play chess, and implement a simple 3D physics fun application where you can flick chess pieces using
physics. You can treat it as a game for 2 people — just play it on a computer, and let each player use the
mouse and keyboard in turn.
In response, we will create a new directory with a few project files that define your
project data and initial Pascal code.
You can explore the files in your project using the bottom panel of the editor.
You can also just explore them using your regular file manager — there’s nothing special
about this directory, these are normal files and directories.
● data is a subdirectory where you should put all the data that has to be loaded at
run-time by your application. All the 3D and 2D models, textures, designs have to
be placed here if you want to use them in your game. Initially it contains the design
called gameviewmain.castle-user-interface (and, less important,
CastleSettings.xml and README.txt files).
The general idea is that the initial application (created from the "Empty" template)
contains just a single view called Main. A view is a Castle Game Engine concept that
represents something that can be displayed in a Castle Game Engine application.
You use it typically quite like a form in Delphi or Lazarus. It is a basic way to organize
your application.
● Every view can be visually designed. Just double-click on it, in the "Open Existing View"
panel or in the "Files" panel (when you’re exploring the data subdirectory).
In larger applications, you can have multiple views. Also, in larger applications,
you can visually design some user interface elements that are not views, but are just
reusable pieces of a user interface. All these files have the extension
.castle-user-interface and can be visually designed using the editor.
The views have, by convention, a name like gameview*.castle-user-interface.
● Every view has also an accompanying Pascal unit. The unit is named like the view,
but without the .castle-user-interface extension. So in our case, the unit is
called gameviewmain.pas. The unit contains the Pascal code that should be
executed when the view is displayed. It defines a class that has virtual methods to
react to various useful events (like view being started, or user pressing a key
or a mouse button).
You will often add more methods to it, to implement your application logic.
See https://castle-engine.io/view_events and
https://castle-engine.io/views to learn more about the views in our engine.
You’re probably itching to start actually doing something after this lengthy introduction.
Let’s get to it.
As a first thing, make sure that everything works. Use the big "Compile And Run" button
(key shortcut F9) and watch as the project is compiled and run. The result will be
boring — dark window with FPS (frames per second) counter in the top-right corner.
FPS are a standard way to measure your application performance.
��The editor by default uses a bundled version of latest stable FPC (Free Pascal Compiler).
If you’d rather use your own FPC installation or Delphi, configure it in the preferences.
��To edit the Pascal files, the editor by default tries to auto-detect various
Pascal-capable IDEs and editors, like Lazarus, Delphi, Visual Studio Code.
If you prefer to configure a specific editor, choose it in the preferences.
More details about the editor configuration can be found in our manual on
https://castle-engine.io/install .
The editor can use any Pascal compiler and any text editor. We deliberately don’t put any
special requirements on what you can use. Though we make sure to support the
popular choices perfectly. In particular, we have a dedicated support for using Visual
Studio Code with Pascal (and Castle Game Engine in particular), see
https://castle-engine.io/vscode .
Let’s add more content to it. First of all, to display anything in 3D, you need a viewport.
A viewport is a way to display 3D or 2D content. It is an instance of TCastleViewport
class. Add it to the design by right-clicking on the Group1 component and choosing "Add
User Interface → Viewport (3D)" from the menu that appears.
Following this, drag the new Viewport1 component above the LabelFps in the Hierarchy
panel (on the left). This way the FPS counter will be displayed in front of the viewport.
Click and hold the right mouse button over the viewport to look around.
Use the AWSD keys to move. Use the mouse scroll (while holding the right mouse
button pressed) to increase or decrease the movement speed.
Play around with moving the items. Drag the 3D axis to move any object.
Play around with adding new 3D items. Right-click on Items component inside the
Viewport1 and from the context menu add primitives like "Box", "Sphere", "Cylinder".
Move them around, delete them (with Delete key), duplicate (with Ctrl+D key).
Change some properties. On the right side, you can see an object inspector, familiar to
any Lazarus and Delphi user. Adjust the properties, for example change the Size of the
Plane1 to be much bigger. Click on "…" (3 dots, called Ellipsis) button at the "Color"
property of any primitive (like a plane, a box, a sphere…) to change the color.
We support a number of 3D and 2D model formats, not only glTF. They are listed on
https://castle-engine.io/creating_data_model_formats.php .
If you are capable of creating your own 3D models, for example in Blender, you can
now make a detour: design a 3D model in Blender and export it to glTF using our
instructions on https://castle-engine.io/blender .
Or you can use some ready-made stuff:
● There’s a number of high-quality 3D content on the Internet, available also for free
and on open-source-compatible licenses. We collect useful links on
https://castle-engine.io/assets.php .
● Our engine also features an integration with Sketchfab, to allow you to search and
download from a vast repository of free 3D models without leaving our editor.
See the https://castle-engine.io/sketchfab documentation.
Here’s a sample — battle-hardened cat model, from Sketchfab, right inside our editor:
Credits: The "Cat" 3D model was done by Muru (https://sketchfab.com/muru) and is available on Sketchfab
(https://sketchfab.com/3d-models/cat-16c3444c8d1440fc97fdf10f60ec58b0) on CC-BY-4.0 license.
Blaise Pascal Magazine 112 2023 36
CASTLE GAME ENGINE ARTICLE PAGE 11 / 18
THE BAD WAY TO PLAY CHESS:
3D PHYSICS FUN USING CASTLE GAME ENGINE (PART 1)
● Finally, we have a ready set of 3D models for the chessboard and all chess pieces,
that you can use for this demo.
To use the last option, download the 3D models from https://github.com/castle-
engine/bad-chess/releases/download/chess-models/chess-models.zip .
They were made based on open-source Blender model published on
https://blendswap.com/blend/29244 by Phuong2647.
Unpack the resulting archive anywhere under the data subdirectory of your project.
Then simply drag-and-drop the *.gltf files onto the viewport. Move and duplicate
them as needed, to arrange them into a starting chess position.
NOTE
For our silly physics game, it actually completely doesn’t matter how you will arrange
them. You also don’t need to position and rotate them perfectly. Have fun :)
glTF (*1) is a standard file format for three-dimensional scenes and models. A glTF file uses one of two
possible file extensions: .gltf (JSON/ASCII) or .glb (binary). Both .gltf and .glb files may
reference external binary and texture resources. Alternatively, both formats may be self-contained by
WIKIPEDIA directly embedding binary data buffers (as base64-encoded strings in .gltf files or as raw byte
arrays in .glb files).
An open standard developed and maintained by the Khronos Group, it supports 3D model geometry,
appearance, scene graph hierarchy, and animation. It is intended to be a streamlined, inter-operable format for
the delivery of 3D assets, while minimizing file size and runtime processing by apps. As such, its creators have
described it as the "JPEG of 3D."
Once you’ve designed the chessboard and put chess pieces on it, also make sure to
adjust the lights to make everything nicely bright (but not too bright).
Finally, adjust the camera so that user sees a nice view of the board when the
application starts. When you select a camera component (like Camera1, if you haven’t
renamed the default camera), the editor shows a small window with camera preview.
You can click "Pin" in this window to keep observing the world from this camera.
There are basically 2 ways to manipulate the camera:
��Move and rotate the camera just like any other 3D object. Look at the camera
preview to judge whether the camera view looks good.
��Or, alternatively, navigate in the editor and then use the menu item
"Viewport → Align Camera To View" (key shortcut Ctrl + Numpad 0) to make the camera
view match the current view in the editor.
Once you have a nice view, make sure it all works: compile and run the application again.
Castle Game Engine has a support for rigid body physics. This means that:
We will not explore all these features in our article, but we will show you how to enjoy
the basics. To learn more about the possibilities, read our manual
https://castle-engine.io/physics and play with demo's in the examples/physics/
subdirectory of the engine. Here’s a screenshot from one of the demos, showing
explicit application of physics forces:
Castle Game Engine physics internally uses Kraft, a physics engine developed in Pascal by
Benjamin 'BeRo' Rosseaux.
��A collider, which stands for any component descending from the abstract class
TCastleCollider. Many collider shapes are possible, like TCastleSphereCollider,
TCastleBoxCollider and TCastleMeshCollider.
Using the TCastleMeshCollider results in most precise collisions, but the colliding
object must be static which means that other objects will bounce off this object,
but the object with TCastleMeshCollider will not move itself.
The term behavior we used above is a special mechanism in Castle Game Engine to
attach additional functionality to a TCastleTransform. Behaviors are a great way to
define various functionality that enhances given game object. There are various built-in
behaviors and you can also define your own.
See https://castle-engine.io/behaviors for more information.
After this overview, you’re ready to actually use physics in our chess game.
Right-click on the component representing the chessboard. From the context menu
choose "Add Behavior (Extends Parent Transform) → Physics → Collider → Mesh".
In response, you will notice that 2 components have appeared in the component tree:
MeshCollider1 and RigidBody1.
That’s a convenience feature of the editor: adding a collider also adds a rigid body
component.
Next choose any chess piece. Right-click on it and from the context menu choose
"Add Behavior (Extends Parent Transform) → Physics → Collider → Box". Note that we use a
simpler collider for the chess piece, which is also dynamic. This will allow the chess
piece to actually fall down on the board.
Finally move the chess piece to a more dramatic position, above the board, so that it
will fall down when the physics will start.
We are ready to run physics. One way would be to just run the application,
using the "Compile And Run" as you’ve done before. But there’s a quicker way to
experiment with physics: run physics simulation by using the green play icon at the
header of the editor (or menu item "Physics → Play Simulation", key shortcut Ctrl+P).
Remember to finish the physics simulation when you’re done (press the green stop
button, or again menu item "Physics → Play Simulation", key shortcut Ctrl+P).
Editing the design during the physics simulation is allowed (and it’s a great way to
experiment with various physics settings) but the changes are not saved when physics
simulation is running. That’s because physics typically moves the objects, and you don’t
want to save this position resulting from physics interactions. So be sure to stop the
physics simulation before doing any persistent changes to the design.
● Move the chess pieces to more interesting positions, so that multiple pieces will
fall down from above on multiple other chess pieces.
● You can also duplicate (key shortcut Ctrl+D) the chess pieces
(it will duplicate the whole selected object, including physics behaviours if any).
That s an easy way to have a lot of physical objects that bounce off each other.
After each change, just play and stop physics simulation again.
Make sure that the initial position of all rigid bodies does not make some pair collide
with each other right at the start. If the two objects will collide at start, physics engine
may (sometimes quite explosively) move them away from each other.
One last thing remains to learn in this (first) part of the article:
how to flick the chess piece?
� ��From Pascal code you can use various methods to apply a force on a rigid body.
More about this in the next article part.
You can also experiment with the example application
examples/physics/physics_forces/ if you’re impatient.
We will use the latter approach, as it can be trivially done and tested in the editor.
● Select the chess piece. Any chess piece you want to "flick"
(throw across the board).
❾ SUMMARY
We have designed a 3D application using Castle Game Engine with a bit of physics.
We didn’t yet write any Pascal code to do any interactions - this will be done in the next part
of the article.
If you want to download a ready application, resulting from this, go to
https://github.com/castle-engine/bad-chess . The subdirectory project of
that repository contains the final working demo of this. It will be extended in the next
part of the article.
I hope you had fun doing this demo and exploring the possibilities of Castle Game Engine.
If you have any questions or feedback about the engine, don’t be shy!
Speak up, ask and share your comments on our forum https://forum.castle-
engine.io or Discord https://castle-engine.io/talk.php .
SPECIAL OFFER € 75
Ex Shipping
+
Blaise
Blaise Pascal
Pascal Magazine
Magazine107/108
112 2023
2022 45
THE LAZARUS DEBUGGER PAGE 1/6
By Martin Friebe
Starter Expert
1. program FindRepeat;
2. uses Math;
3.
4. const
5. TESTDATA = 'Test a random text. Repeat: a random text';
6.
7.
8. function EqualSubText(AText: Ansistring; AStart1, AStart2, AMaxLen: Integer): AnsiString;
9. var
10. EqualLen: Integer;
11. begin
12. EqualLen := 0;
13. while (AMaxLen > EqualLen) and (AText[AStart1 + EqualLen] = AText[AStart2 + EqualLen]) do
14. inc(EqualLen);
15.
16. Result := copy(AText, AStart1, EqualLen);
17. end;
18.
19. (* DoFindLongestRepeat
20. AStart1: Iterates over all potential start positions for the first match of the text
21. AMaxSearchLen1: Count of chars up to the start of for the second match
22. AStart2: Iterates over all potential start positions for the second match of the text
23. Must always be greater the AStart1
24. (or equal, which will be handled by "AMaxSearchLen1 = 0")
25. AMaxSearchLen2: Count of chars up to the end of the string
26. *)
27. function DoFindLongestRepeat(AText, AFound: Ansistring;
28. AStart1, AMaxSearchLen1,
29. AStart2, AMaxSearchLen2: Integer
30. ): Ansistring;
31. var
32. EqualTxt: String;
33. begin
34. Result := AFound;
35.
36. EqualTxt := EqualSubText(AText, AStart1, AStart2, Min(AMaxSearchLen1, AMaxSearchLen2));
37. if Length(EqualTxt) > Length(Result) then
38. Result := EqualTxt;
39.
40. if AMaxSearchLen2 > 1 then
41. Result := DoFindLongestRepeat(AText, Result,
42. // AStart2 increases, so there is one more char available after AStart1
43. AStart1, AMaxSearchLen1 + 1,
44. // And there is one char less after AStart2
45. AStart2 + 1, AMaxSearchLen2 - 1
46. )
47. else
48. if AStart1 < Length(AText) - 1 then begin
49. Result := DoFindLongestRepeat(AText, Result,
50. // AStart2 is set equal to AStart1, so AMaxSearchLen1 will be 0
CONTINUATION
51. AStart1 + 1, 0,
52. // AStart2 will be 1 more than AStart1 was,
53. // so there will be 1 char less to search after AStart2
54. AStart1 + 1, AMaxSearchLen1 - 1
55. ); 56. end;
57. end;
58.
59. function FindLongestRepeat(AText: Ansistring): Ansistring;
60. begin
61. Result := DoFindLongestRepeat(AText, '',
62. 1, 0, // AStart1 equals AStart2: There are 0 chars between
63. 1, Length(AText) // AStart2 has the entire string
64. );
65. end;
66.
67. begin
68. writeLn('"' + FindLongestRepeat(TESTDATA) + '"');
69. readln;
70. end.
Before we continue tracking the wrong value, let’s have a look at the
contents of the new window and what information it provides.
The top line is line 38 (column “Line”) at which our app is currently paused.
The line below is showing from where the current invocation of
“DoFindLongestRepeat” was called. As we are in a recursion, the function did
call itself. However, the call was invoked from line 41.
Looking at all the columns in the grid.
� The first column shows, if the line has a breakpoint. In our case this
applies to line 38. But had we had a breakpoint at line 41,
it would be shown on the other lines.
� The 2nd column “I…” (Index) is a running number, showing us how many
calls we are away from the top. This can be useful,
when scrolling through a very long list.
� “Location” and “Line” are the unit (filename) and line-number.
If they aren’t known, an address may be shown.
� “Function” is the name of the routine. In case of a method it will be in the
“classname.method” notation. This column also contains the values of
the parameters passed. (Please see the note on params and locals
in the section “The full stack”)
For the top line – representing the function in which the project is currently
paused – we have “5, 23, 28, 10” for “AStart1, AMaxSearchLen1, AStart2,
AMaxSearchLen2”. So as we saw “AMaxSearchLen2”=10.
And for the direct caller we have “5, 22, 27, 11”. The caller had checked for
repeated text at the position one char earlier (“AStart2”=27) and it had up to
11 chars (“ AMaxSearchLen2”) to check. Comparing the caller's total of
“27 + 11” with the current “28 + 10”, both functions are the same amount short
of the actual full length of the text.
Looking down through the stack on each line “AStart2” goes one down,
and “AMaxSearchLen2” goes one up. However, we only see the top 10 callers,
and the relevant information may be further away. We can get more lines, if
we press the button, or use the “Max 10” drop-down. Lets use the
“Max 10” drop-down and select 50 entries.
(If we need more we need the button )
We scroll down until we find a break in the pattern of +/-1.
This happens for the caller from line 49.
Looking at index 23 called by 24, we can see that “AStart1” was incremented
to 5 (where the first occurrence of “ a random text” starts),
and “AStart2” set to start from 5 too. Checking at index 24 we find that “AStart2”+”
AMaxSearchLen2” = “38 + 1” = 39.
Not the full length, but 1 more than “27 + 11” = 38.
So during this call we lost 1 char from “AMaxSearchLen2”.
Looking at the code
Result := DoFindLongestRepeat(AText, Result,
AStart1 + 1, 0,
// AStart2 will be 1 more than AStart1 was,
// so there will be 1 char less to search after AStart2
AStart1 + 1, AMaxSearchLen1 - 1
);
Making “FindLongestRepeat” the current frame, and we can see that the
locals window no longer shows AStart.../AMaxSearchLen variables.
The locals show “AText” instead, which is the parameter passed to
“FindLongestRepeat”.
We also see that we can trace back all the way to the program's
“begin...end.” block shown as “$main”. And the index tells us, that our
recursion is a 175 calls deep at the time of hitting the breakpoint.
Sometimes stack traces are much deeper than that, and in that case using
the button to reach the bottom of the stack can be tedious.
In this case the blue up/down buttons can help to navigate quickly to
the top and bottom. And the edit field with the button can be used to
enter any index and show frames starting from it.
On the topic of navigation, the stack window also allows us to navigate in
the source code. Double clicking any line in the stack (or using the
button) will bring up the code in the source editor.
Of course only, if the stackframe has a source-file and line-number.
The button will copy all entries to the clipboard. And the power button
will freeze the currently shown entries. When power is off, the stack
window will not update when you step/run the application. In case you
want to keep the current frame list as a reminder or something like that.
SUMMARY
The callstack can be used to inspect locals from any caller. It can also show
us who called the current function.
maXbox
EXECUTING PROGRAMS ARTICLE PAGE 1 / 18
ON THE SERVER IN
By Michael Van Canneyt
Starter
Expert
ABSTRACT
In this article we show how to give the user of a browser-based program
feedback from long-running processes on the server, using 2 components:
one in PAS2JS, one in Free Pascal/Lazarus.
� INTRODUCTION
When using a web-based program, not everything can be done in the
browser.
Often,tasks are executed through some RPC (Remote Procedure Call)
mechanism on the webserver. This can be a simple task such as executing
an SQL statement on a database and returning a result. Or it can be a more
complicated and time-consuming task such as making a backup of a
database, indexing PDF files, compiling a software project and running a
test suite, or even installing software on the server. Ideally, the output of
these remote programs should also be presented to the user.
To keep programs scalable, these tasks should be short-lived. A return time
of 1second for a HTTP request is already a long time, so executing a time-
consuming task and waiting for the return using a single HTTP request is
not a good idea:
the HTTP server is occupied with the request, the browser or any proxy
servers between the HTTP server and the browser may decide to time-out
your request.
Much better is to start the process using a HTTP request, and use a
mechanism to poll the status of the executed process. In this article we
present one such mechanism.
� ARCHITECTURE
The solution we present here consists of 2 components. One component
which is used on the server, and which can be used to start a process,
capture its output and poll for the status of the process. The other
component takes care of the polling process on the client.
These components are ignorant of the communication mechanism between
browser and server, this means that they do not implement the actual RPC
calls used to start the process: There are many possible mechanisms,
and some may be more suitable for your purpose than others.
The components are called TProcessCapture for the server part and
TProcessCapturePoller for the client (PAS2JS) part. The server part takes
care of executing a program and redirecting the output to a file, the client
part implements the polling mechanism and some callbacks to handle the
actual server calls and the result. We’ll demonstrate both components with
a simple set of programs:
● A test program to be executed.
It is used for demonstration purposes only.
● A HTTP server program that allows to serve
HTML files and that offers an
● RPC mechanism to start the test program and
handle status requests. A Simple PAS2JS program
that will run in the browser and which will remotely
execute the test program. It will show the output of
the test program in the browser.
We’ll start with the test program.
Blaise Pascal Magazine 112 2023 54
EXECUTING PROGRAMS ARTICLE PAGE 2 / 18
ON THE SERVER IN
❸ THE TEST PROGRAM
To demonstrate the workings, the test program needs to do 3 things:
� It must run for some time, several seconds at least.
This is done with a simple loop and a call to sleep.
� needs to show that it receives command-line arguments:
we will simply output the program parameters.
❸ It must demonstrate that it is run in a specific directory.
We’ll just print the working directory.
❹ It needs to produce some output.
var
i : integer;
D : TDateTime;
begin
Writeln(’Current dir: ’, GetCurrentDir);
Write(’Args:’);
For I:=1 to ParamCount do
Write(’ ’,ParamStr(i));
Writeln();
D:=Now;
For I:=1 to 150 do
begin
Sleep(100);
Writeln(’Tick ’,i);
Flush(output);
end;
Writeln(SecondsBetween(Now,D), ’ seconds elapsed’);
flush(output);
end;
The only noteworthy thing about this program is that it flushes standard
output after writing a line: By default, Free Pascal buffers output of writeln
statements if it detects that it is not writing to a console. Since our program
will be run with the output redirected, the buffering will be activated, and
so, in order to send the output faster to the browser, we flush standard
output manually.
To work with this component, you will typically perform the following steps:
� Set appropriate values for LogDir, InputFile, WorkingDir and OutputCodePage.
They contain sensible defaults, but it is better to be explicit.
� Start the program using the Execute method,
and save the resulting ProcessID string.
❸ Initialize an offset variable to zero.
❹ Check if the process is still running with IsProcessRunning, passing it ProcessID.
❺ Get the output of the process using GetProcessOutput, passing it ProcessID
and the current offset. Update the offset.
❻ Repeat the last 2 steps till the program exits.
It should be noted that you can free the TProcessCapture after every step
and recreate it before performing a call: it is stateless. This is necessary if
the component is to work in a web environment where the different steps
will be performed as part of different HTTP requests: the steps may be
performed by different instances of the application server.
To work correctly, the LogDir and OutputCodePage properties must be set
to the same values between invocations.
It also means that the same component can be used to control different
processes. Although this is not recommended if you use threads: the
component is not re-entrant.
To do its work, the TProcessCapture component executes a small helper
program called taskhelper: this program does the work of launching the
actual program that needs to be executed with redirected in and output. It
also takes care of registering the exit status of the program.
On Unix platforms, it is possible to do without this program, but on
Windows, the mechanism to start a new process CreateProcess
necessitates the use of an extra program.
To make the behavior across platforms consistent, the taskhelper
program is used everywhere.Its sources are distributed with the trunk
version of FPC, but the source has been included in the sources of this article.
The latter is strictly speaking not necessary since the component is owned
by the datamodule and will be destroyed when the datamodule is
destroyed, but for clarity we destroy it manually anyway.
In the OnExecute event of the StartProcess handler, we collect the 2
arguments A and B and start the test program:
const
LongProcess = ’longprocess’ {$ifdef windows} + ’.exe’ {$endif} ;
var
arr : TJSONArray absolute Params;
a, b, Exe, PID : string;
begin
Res:=Nil;
a:=Arr.Strings[0];
b:=Arr.Strings[1];
Exe:=ExtractFilePath(ParamStr(0))+longprocess;
PID:=Capture.Execute(Exe,[a,b]);
Res:=TJSONString.Create(PID);
As you can see in this code, we use the Execute method of the
TProcessCapture class to start the process.
For the GetStatus call, the code is a little longer, but not so much.
The code starts by getting the arguments, and checking the whether the
process is still running. If the process is no longer running, then the exit
status is retrieved.
Regardless of whether the process was still running or not, finally the
available output is retrieved and all 3 elements (status, output, new offset)
are returned to the client in a JSON object.
The data module will look like figure 3 on page 6 of this article.
Before the program can be used, there are two last things to be done when
using the release version of FPC on Linux. The HTTP connection on which
requests arrive is passed to the task helper, and as a consequence the
connection is not closed
when the StartProcess call returns, causing the browser to wait till the
process exits. This of course defeats the purpose of the whole exercise.
To remedy this, we must set the Close-On-Exec flag on the socket handle.
This can be done easily by handling the OnAllowConnect handler of the
HTTP server.
To do so, we add the following to the project file:
THTTPApplication = Class(fphttpapp.THTTPApplication)
constructor Create(aOwner : TComponent); override;
private
procedure DoConnect(Sender: TObject; ASocket: Longint; var Allow: Boolean);
end;
{ THTTPApplication }
Lastly, to serve the files of the client program, we set the base directory for
the file serving module to the directory with the client program files:
(this code assumes there are 2 directories: one for the server, one for the
client.) Finally, we load all known mime types, and create our own HTTP
application:
Var
Application:THTTPApplication;
begin
MimeTypes.LoadKnownTypes;
TSimpleFileModule.BaseDir:=GetBaseDir;
TSimpleFileModule.RegisterDefaultRoute;
Application:=THTTPApplication.Create(Nil);
Application.Title:=’Process server’;
Application.Port:=8060;
Application.Initialize;
Application.Run;
Application.Free;
end;
Type
TProcessStatus = (psRunning, // Process still running
psExited, // Process has exited
psError // Too many errors
);
TOnGetProcessStatusEvent =
Procedure (Sender : TObject; aProcessID : String; aOffset : NativeInt)
TOnProcessDoneEvent =
Procedure (Sender : TObject; aStatus : TProcessStatus; aExitCode : Integer)
TOnProcessOutputEvent =
Procedure (Sender : TObject; aOutput : String) of object;
TOnStatusFailEvent =
Procedure (Sender : TObject; aError : String) of object;
TProcessCapturePoller = class(TComponent)
The following simple HTML (using Bulma CSS) will do the job just fine:
<h3 class="title is-3">Process output demo</h3>
<div class="box">
<h4 class="title is-4">Start parameters</h4>
<div class="field">
<label class="label">Argument A</label>
<div class="control">
<input id="edtA" type="text" class="input"
placeholder="Enter argument A">
</div>
</div>
<div class="field">
<label class="label">Argument B</label>
<div class="control">
<input id="edtB" type="text" class="input"
placeholder="Enter argument B">
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button id="btnStart" class="button is-primary">
Start process
</button>
</div>
<div class="control">
<button id="btnCancel" class="button is-warning is-light">
Cancel
</button>
</div>
</div>
</div>
<div class="box">
<h4 class="title is-4">Process output</h4>
<div id="pasjsconsole">
</div>
</div>
To interact with this HTML, we first create a HTML Fragment module using
the ’File - new’ dialog. We name it ’frmIndex’ and set the ’UseProjectHTML’
property to True. On this module, we drop a THTMLElementActionList
component from the component palette. Using the component context
menu ’Create actions for HTML tags’, we can create actions for all tags in the
above HTML, as shown in figure 5 on page 12 of the article.
We need a TPas2jsRPCClient from the Pas2JS tab in the component
palette: this component will handle the RPC requests, and we’ll name it RPC
for short. The component can only do its work correctly if it knows where
the server is: We need to enter the URL property. As shown in an earlier
article, we can now generate a service proxy: this is a class which has
correct method definitions, reflecting the methods defined in our RPC
server. Calling these service methods will actually execute the methods on
the server. Right-clicking on the RPC component and selecting ’Create Service
Client component’ shows the service generation dialog as shown in figure 6
on page 13 of the article. We name the unit ’processservice’ and tell the
IDE to add it to the project.
Now we can start coding the application. We will create the
TProcessCapturePoller and service client in the OnCreate event of our
index form module:
procedure TfrmIndex.DataModuleCreate(Sender: TObject);
begin
Service:=TprocesscontrolService.Create(Self);
Service.RPCClient:=RPC;
FPoller:=TProcessCapturePoller.Create(Self);
FPoller.OnProcessOutput:=@DoDoutput;
FPoller.OnGetProcessStatus:=@DoGetStatus;
FPoller.OnProcessDone:=@DoProcessDone;
FPoller.OnStatusFail:=@DoStatusFail;
end;
Note that we assign the RPC client to our service definition, and that we
assign events to all event handlers of the poller component.
To start the process, we add an OnClick event handler to the actbtnStart
action.In it, we collect the values for the A and B parameters from the
respective input boxes, and use these to call StartProcess on our Service
component.
We take care to handle the OnSuccess and OnFail handlers of this method
- remember, the calls to the server are asynchronous:
var
a,b : string;
begin
a:=actedtA.Value;
b:=actedtB.Value;
Service.StartProcess(A,B,@DoStartOK,@DoStartFail);
end;
If the start call fails, we simply log the fact. If the start call succeeds, we record the
result (a process ID) in the poller ProcessID property and start the poller. The onclick
handler for the ’Cancel’ button is much simpler:
We just need to cancel the poller.
procedure TfrmIndex.actbtnCancelExecute(Sender: TObject; Event: TJSEvent);
begin
Writeln(’Canceled wait for process.’);
FPoller.Cancel;
end;
Var
D : TJSObject absolute aResult;
aExitCode : Integer;
aNewOffset : NativeInt;
aOutput : string;
aStatus : TProcessStatus;
begin
aOutput :=String(D[’output’]);
aExitCode :=NativeInt(D[’status’]);
aNewOffset :=NativeInt(D[’offset’]);
aStatus :=Statuses[aExitCode=-1];
FPoller.ReportProgress(aStatus,
aOutput,aExitCode,aNewOffset)
end;
begin
Service.GetStatus(FJobID,aOffset,
@DoStatusOK,@DoStatusFail);
end;
begin
Service.StartProcess(A,B,@DoStartOK,@DoStartFail);
end;
Note that if the process failed to start, the fail count is set to the
maximum, this will cause the ReportProgressFail method not to
schedule a new check. The DoStatusCheck method contains simply the
code that was present in the form in our first implementation:
procedure TRemoteExecutor.DoStatusCheck;
The form code is now much simpler. We only need to create the
TRemoteExecutor component, and set its 3 events:
begin
FRemote.Execute(actedtA.Value,actedtB.Value);
end;
❾ CONCLUSION
In this article we’ve shown that executing programs on a HTTP Server from a Pas2JS
program does not need to be difficult. The component to automate the process is
independent of a RPC mechanism, and as such can be used as-is, or it can be used as
the parent for a more elaborate component which handles all communication by itself.
ONLY AT
BARNSTEN
ENDING 30
SEPTEMBER
Delphi & C++Builder are the best development tools on the
market to design and develop modern, cross-platform native apps
and services. Also for Windows 11! It’s easier than ever to create
stunning, high performing apps for Windows, macOS, iOS,
Android and Linux Server (Linux Server is supported in Delphi
Enterprise or higher), using the same native code base.
30% discount on all licenses. This offer is valid until September 30,
2023
BARNSTEN
Order online or ask us for a quote.
1 2
DIRECT SEARCH
OVER 6.500 PAGES OF ARTICLES AND CODE
PAGE-CLARK
LAZARUS
LEARN TO PROGRAM
HOWARD
HANDBOOK
3 5 6
F OR P ROGRAM M ING WIT H F RE E P ASCAL AND LAZARUS
USING LAZARUS
POCKET
Edition
+shipment
LAZARUS
HANDBOOK
PDF
4 934 PAGES
COMPONENTS
DEVELOPERS
Donate for Ukraine and get a free license at:
4
https://components4developers.blog/2022/02/26/
donate-to-ukraine-humanitarian-aid/
COMPONENTS
4
Blaise Pascal Magazine 112 2023
DEVELOPERS
D11 75
Do n a t e f o r U k r a i n e a n d g et a f r ee l i c en s e a t :
https://components4developers.blog/2022/02/26/donate-to-ukraine-humanitarian-aid/
COMPONENTS
Blaise Pascal Magazine 112 2023
DEVELOPERS 4 ADVERTISEMENT
D11 76