Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - Quentin

#21
STDOUT "? ? ? ?\n"

klappt nicht. STDIN liest korrekt ein. Mit STDIN eingelesene Zeichenketten werden korrekt eingelesen und anschlie?end mit STDOUT auch korrekt ausgegeben.
#22
hehe, so allm?hlich flie?en immer mehr Anwenderw?nsche ein. Auch wenn ich pers?nlich wohl nicht allzu viel mit Konsolenanwendungen machen werde, finde ich es trotzdem klasse, wie schnell hier reagiert wird.

Merci bi?ng Gernot

wenn jetzt auch noch irgendwann das Entitysystem weiter ausgebaut und in den Standard-Befehlsumfang integriert wird, falle ich hier glatt jemandem zu F??en :)
#23
on Windows you can use "ShellExecuteA" from shell32.dll for this.


Code (glbasic) Select

OpenBrowser("http://www.glbasic.com/forum/")


FUNCTION __dummy:
ENDFUNCTION

INLINE
DECLARE(ShellExecuteA, "shell32.dll", (void*, const char*, const char*, const char*, const char*, int), int);
ENDINLINE

FUNCTION OpenBrowser: url$
INLINE
ShellExecuteA(GLBASIC_HWND(), "open", url_Str.c_str(), "", "", 0);
ENDINLINE
ENDFUNCTION
#24
Per Zufall bin ich über etwas gestolpert, wo ich erst mal genau hinschauen musste, um auf den Fehler zu kommen.

Beispiel:
Ein mehrzeiliger Befehl mit "_" verknüpft
Code (glbasic) Select

PRINT "Das hier ist eine " + _
      "lange Zeile", _
      0, 0
SHOWSCREEN
KEYWAIT


Dann die ersten beiden Zeilen des PRINT-Befehls auskommentieren, die "0, 0" löschen und durch einen Befehl ersetzen
Code (glbasic) Select

//PRINT "Das hier ist eine " + _
//      "lange auskommentierte Zeile", _
PRINT "das hier wird nicht gezeigt", 0, 20
SHOWSCREEN
KEYWAIT


Jetzt wird der neue PRINT-Befehl niemals ausgeführt. Scheinbar wird der Unterstrich auch bei Kommentaren geprüft. Es wird aber auch kein Fehler beim Kompilieren gezeigt, wenn man nach dem Kommentar mit "_" am Ende irgendeinen Unsinn eingibt.
Code (glbasic) Select

//PRINT "Das hier ist eine " + _
//      "lange auskommentierte Zeile", _
irgend ein unsinn
SHOWSCREEN
KEYWAIT


Ist jetzt nicht dramatisch, lässt einen aber an sich selbst zweifeln 

#25
Hi all,

some time ago I've started to play with the Entity system written by Gernot. So I decided to write a little to tutorial how to handle it. I think, with this system 3D programming will get as simple as it can be.
The tutorial will show how to implement the basics of a space shooter. You can fly your ship in space and shoot some asteroids. Hope some of you find it helpful.

In the attachment you'll find all project files needed and the tutorial text as a PDF file.

[EDIT]
FutureCow has corrected my translation, so its  in a better readable form I think.

have fun
Quentin

[attachment deleted by admin]
#26
Hallo liebe GLBasic'ler,

da ich mich schon seit längerem mit dem Entity-System beschäftige (um endlich den nächsten X-Wing-Nachfolger rauszubringen ;) ) habe ich mal ein kleines Tutorial verfaßt, daß die Grundzüge des Entity-Systems anhand eines einfachen Weltraum-Shooters zeigen soll. Man kann sich hier frei im 3D-Weltraum bewegen und bunte Asteroiden abschießen, die planlos in der Gegend rumschwirren. Also nichts großes, aber vielleicht kann der ein oder andere etwas damit anfangen.

Im Anhang befinden sich gepackt das Tutorial als PDF-Dokument sowie die erforderlichen Projektdatein.

Viel Spaß damit.

---------------------
Update 1.01
- Lasergeschütze als Pivotelemente
- Funktion für Kollisionserkennung optimiert (FOREACH)
- Anhang aktualisiert

[attachment deleted by admin]
#27
sollte das nicht gefeiert werden??
#28
In der Hilfe ist beschrieben, daß man TYPE's als Funktionsargumente als auch als Rückgabewerte von Funktionen nutzen kann. Somit liegt doch die Vermutung nahe, daß man beides auch kombinieren kann, da Funktionsaufrufe generell ja geschachtelt werden können. Ich habe also eine Funktion, die einen TYPE zurückliefert. Wenn ich jedoch diese Funktion als Funktionsargument einer zweiten Funktion mitgebe, erhalte ich einen Fehler "(9) error : wrong argument type : PrintType, arg no: 1"

Einfaches Beispiel:
Die Funkton MakeType erstellt eine Variable vom Typ t_type. Die Funktion PrintType soll die Werte ausgeben und erwartet einen Parameter vom Typ t_type. Erstelle ich erst eine Variable vom Typ t_type und übergebe diese als Parameter an PrintType, funktioniert das. Übergebe ich als Parameter gleich den Funktionsaufruf, erhalte ich den Fehler. (geschrieben mit Version 5.360)

Code (glbasic) Select

TYPE t_type
a; b; c
ENDTYPE

LOCAL ty AS t_type

//ty = MakeType(5, 3, 7)
//PrintType(ty) <----- das funktioniert
PrintType(MakeType(5, 3, 7))  // <----- das nicht
SHOWSCREEN
KEYWAIT


// ------------------------------------------------------------- //
// ---  MAKETYPE  ---
// ------------------------------------------------------------- //
FUNCTION MakeType AS t_type: a, b, c
LOCAL lt AS t_type
lt.a = a
lt.b = b
lt.c = c
RETURN lt
ENDFUNCTION // MAKETYPE


// ------------------------------------------------------------- //
// ---  PRINTTYPE  ---
// ------------------------------------------------------------- //
FUNCTION PrintType: pt AS t_type
PRINT pt.a, 0, 0
PRINT pt.b, 0, 20
PRINT pt.c, 0, 40
ENDFUNCTION // PRINTTYPE
#29
Besteht eine Möglichkeit, aus mit glNewList() generierten Modellen .ddd-Dateien zu speichern?

Latürnich könnte man zunächst .dda-Dateien erzeugen und die in .ddd umwandeln, aber das erscheint mir etwas umständlich.
#30
spiele zur Zeit ein wenig mit den gewrappten OpenGL-Funktionen rum. Zwei Funktionen werden angemeckert und zwar

glPolygonStipple
glGetPolygonStipple

Habe diese beiden Funktionen auskommentiert. Der Rest läuft dann anstandslos.

Fehlermeldung:
Code (glbasic) Select

compiling:
C:\DOKUME~1\Quentin\LOKALE~1\Temp\glbasic\gpc_temp1.cpp: In function `DGInt __GLBASIC__::glPolygonStipple(__GLBASIC__::DGIntArray&)':
C:\DOKUME~1\Quentin\LOKALE~1\Temp\glbasic\gpc_temp1.cpp:279: error: no match for call to `(__GLBASIC__::DGIntArray) (int&, int&)'
C:/dev/GLBasic/Compiler/platform/Include/glb.h:300: note: candidates are: DGInt& __GLBASIC__::DGIntArray::operator()(int)
C:/dev/GLBasic/Compiler/platform/Include/glb.h:339: note:                 __GLBASIC__::DGIntArray& __GLBASIC__::DGIntArray::operator()()
*** FATAL ERROR - Bitte die Compiler-Ausgabe ins Forum kopieren
#31
Eine Sache mit der Texturierung von Objekten ist mir noch nicht so ganz klar. In jedem Beispiel, sei es hier in GLBasic (Samples) oder in OpenGL-Tutorien, werden als Texturkoordinaten immer nur 0 und 1 verwendet. Wenn ich jedoch Objekte unterschiedlicher Größe habe und belege beide Objekte mit der gleichen Textur, so erscheinen die Texturen auch in unterschiedlicher Auflösung, wenn ich als Koordinaten nur 0 und 1 verwende. Wenn ich jedoch die Texturkoordinaten im Verhältnis umrechne (also 1 = 100%, Dreisatz ;), dann erscheint die Textur auf allen Flächen in der gleichen Auflösung, egal wie groß die Fläche ist.

Folgendes Beispiel soll das verdeutlichen:

zunächst werden zwei Quadrate gezeichnet, eines mit 100 Punkten Kantenlänge, eines mit 50. Als Texturkoordinaten verwende ich bei beiden nur 0 und 1.

die zwei Quadrate darunter haben die gleiche Dimension, die Texturkoordinaten für das kleinere Quadrat wurden jedoch im Verhältnis angeaßt. (die verwendete Textur findet sich im Anhang)

Code (glbasic) Select

LOADSPRITE "mauer.png", 0

c = RGB(255, 255, 255)
X_OBJSTART 0
X_OBJADDVERTEX -100, 100, 0, 0, 0, c
X_OBJADDVERTEX -100,   0, 0, 0, 1, c
X_OBJADDVERTEX    0, 100, 0, 1, 0, c
X_OBJADDVERTEX    0,   0, 0, 1, 1, c
X_OBJEND

X_OBJSTART 1
X_OBJADDVERTEX    0,  50, 0, 0, 0, c
X_OBJADDVERTEX    0,   0, 0, 0, 1, c
X_OBJADDVERTEX   50,  50, 0, 1, 0, c
X_OBJADDVERTEX   50,   0, 0, 1, 1, c
X_OBJEND

X_OBJSTART 2
X_OBJADDVERTEX -100,  -10, 0, 0, 0, c
X_OBJADDVERTEX -100, -110, 0, 0, 1, c
X_OBJADDVERTEX    0,  -10, 0, 1, 0, c
X_OBJADDVERTEX    0, -110, 0, 1, 1, c
X_OBJEND

X_OBJSTART 3
X_OBJADDVERTEX   0,   -10, 0, 0, 0, c
X_OBJADDVERTEX   0,   -60, 0, 0, 0.5, c
X_OBJADDVERTEX  50,   -10, 0, 0.5, 0, c
X_OBJADDVERTEX  50,   -60, 0, 0.5, 0.5, c
X_OBJEND

GLOBAL KEY_LEFT  = 203
GLOBAL KEY_RIGHT = 205
GLOBAL KEY_UP    = 200
GLOBAL KEY_DOWN  = 208
GLOBAL KEY_W     = 17
GLOBAL KEY_S     = 31

GLOBAL camx, camy, camz, camdx, camdy, camdz
GLOBAL angle, factor

camy = 0

camdx = 0
camdy = 0
camdz = 0

angle = 90
distance = 300


WHILE TRUE

IF KEY(KEY_LEFT)
DEC angle, 1
ENDIF
IF KEY(KEY_RIGHT)
INC angle, 1
ENDIF

IF KEY(KEY_W) THEN DEC distance, 3
IF KEY(KEY_S) THEN INC distance, 3

IF KEY(KEY_UP) THEN DEC camy, 5
IF KEY(KEY_DOWN) THEN INC camy, 5

camx = COS(angle) * distance
camz = SIN(angle) * distance

X_MAKE3D 1, 1000, 45
X_CAMERA camx, camy, camz, camdx, camdy, camdz
FOR i = 0 TO 3
X_SETTEXTURE 0, -1
X_DRAWOBJ i, 0
NEXT

SHOWSCREEN

WEND


wie man sieht, hat im unteren Beispiel die Textur auf beiden Flächen die gleiche Auflösung. Somit wirken auch unterschiedliche große Stücke der "Mauer" wie aus einem Guß. Im obigen Beispiel dagegen meint man, im rechten Mauerteil wären kleinere Steine verbaut worden.

Meine eigentliche Frage:
Ist das der Weg, wie man das Ziel "gleiche Texturauflösung auf unterschiedlich großen Flächen" erreicht? Oder gibt es da einfachere oder bessere Wege?
#32
Da wir zur Zeit unser Häuschen umbauen und wir uns das (zwecks Farbgebung) besser vorstellen können wollen, hab ich spaßeshalber mal ein ein 3D-Model des Hauses erstellt und zwar mit XOBJ.. - Befehlen. Das klappt auch schon ganz gut. Allerdings fällt mir auf, daß, wenn ich Objekte, die umittelbar nebeneinander liegen sollen, z.B. Mauer und Fenster, einen unschönen Zwischenraum aufweisen.

Um das zu verdeutlichen, habe ich mal ein kleines Beispiel gemacht, bei dem dieser Effekt auch auftritt. Einfach ein Stück Mauer als Objekt 0 und die entsprechende Tür als Objekt 1. Beide sollten eigentlich randlos ineinander übergehen. Am linken Rand der Tür zeigt sich jedoch eine kleine Lücke, die ich mir beim besten Willen nicht erklären kann.

Mache ich hier prinzipiell etwas falsch? Also altbekannte Frage: Bug oder Feature?

(mit den Cursortasten kann man das Modellchen um den Mittelpunkt drehen. Mit "W" näher heranzoomen, mit "S" wieder weiter weg zoomen)

Code (glbasic) Select

// --------------------------------- //
// Project: Test_XOBJADDVERTEX
// Start: Friday, November 14, 2008
// IDE Version: 5.360

c = RGB(0, 255, 0)
d = RGB(0, 0, 255)


X_OBJSTART 0
X_OBJADDVERTEX -100,    0,    0,    0,  100, c
X_OBJADDVERTEX -100,  100,    0,    0,    0, c
X_OBJADDVERTEX  -50,    0,    0,   50,  100, c
X_OBJADDVERTEX  -50,  100,    0,   50,    0, c
X_OBJNEWGROUP
X_OBJADDVERTEX  -50,   80,    0,    0,   20, c
X_OBJADDVERTEX  -50,  100,    0,    0,    0, c
X_OBJADDVERTEX   50,   80,    0,  100,   20, c
X_OBJADDVERTEX   50,  100,    0,  100,    0, c
X_OBJNEWGROUP
X_OBJADDVERTEX   50,    0,    0,    0,  100, c
X_OBJADDVERTEX   50,  100,    0,    0,    0, c
X_OBJADDVERTEX  100,    0,    0,   50,  100, c
X_OBJADDVERTEX  100,  100,    0,   50,    0, c
X_OBJEND

X_OBJSTART 1
X_OBJADDVERTEX  -50,    0,    0,    0,   80, d
X_OBJADDVERTEX  -50,   80,    0,    0,    0, d
X_OBJADDVERTEX   50,    0,    0,  100,   80, d
X_OBJADDVERTEX   50,   80,    0,  100,    0, d
X_OBJEND


GLOBAL KEY_LEFT  = 203
GLOBAL KEY_RIGHT = 205
GLOBAL KEY_UP    = 200
GLOBAL KEY_DOWN  = 208
GLOBAL KEY_W     = 17
GLOBAL KEY_S     = 31

GLOBAL camx, camy, camz, camdx, camdy, camdz
GLOBAL angle, factor

camy = 50

camdx = 0
camdy = 50
camdz = 0

angle = 90
distance = 200


WHILE TRUE

IF KEY(KEY_LEFT)
DEC angle, 1
ENDIF
IF KEY(KEY_RIGHT)
INC angle, 1
ENDIF

IF KEY(KEY_W) THEN DEC distance, 3
IF KEY(KEY_S) THEN INC distance, 3

IF KEY(KEY_UP) THEN DEC camy, 5
IF KEY(KEY_DOWN) THEN INC camy, 5

camx = COS(angle) * distance
camz = SIN(angle) * distance

X_MAKE3D 1, 1000, 45
X_CAMERA camx, camy, camz, camdx, camdy, camdz
X_AMBIENT_LT 0, RGB(255, 255, 255)
X_DRAWAXES 0, 0, 0
X_DRAWOBJ 0, 0
X_DRAWOBJ 1, 0

SHOWSCREEN

WEND
#33
inspired by the question of Stu_C about the operation method of SPLITSTR

http://www.glbasic.com/forum/index.php?topic=2384.0

I've tried to implement a modified version of SPLITSTR, which will not ignore multiple splitter without any data between them. As AndyH noted, this could be useful for processing csv files.

The first version of TrueSplitStr, that's how I called it ;), looks like follows:

Code (glbasic) Select

FUNCTION TrueSplitStr: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]

l_len_text = LEN(p_text$)

DIM p_split$[0]                           // initialize p_spit$[]

FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
DIMPUSH p_split$[], l_item$       // yes then continue with the next word
l_item$ = ""
ENDIF
NEXT
DIMPUSH p_split$[], l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


The doubts about performance mentioned in the thread named above, are not unjustified. This version of SplitStr needs 2-3 times more runtime than the original command. One reason is the DIMPUSH command. Depending on the count of needed array elements it must allocate new memory and must preserve the existing array content.

But if I can calculate the number of needed array elements, I'm able to create the array with only one DIM command and fill the array within the FOR-NEXT-loop.

This coding is bigger but much faster than the first version:

Code (glbasic) Select

FUNCTION TrueSplitStr_Fast: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]
LOCAL l_count                             // number of entries needed for p_split$[]
LOCAL l_idx                               // current index of p_split$[]

l_len_text = LEN(p_text$)

// first get the needed number of entries for p_split$[]
FOR l_ctext = 0 TO l_len_text - 1
IF INSTR(p_delim$, MID$(p_text$, l_ctext, 1)) > -1
INC l_count, 1
ENDIF
NEXT
REDIM p_split$[l_count + 1]

// now do the split
FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
p_split$[l_idx] = l_item$         // yes then continue with the next word
l_item$ = ""
INC l_idx, 1
ENDIF
NEXT
p_split$[l_idx] = l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


First the size of the array is calculated. Then it's filled.

The following coding shows all three versions of SplitStr

- SPLITSTR command of GLBasic
- slow version of TrueSplitStr
- fast version of TrueSplitStr

and shows the needed time. As an example I've attached a csv file (example.txt), which is composed by 1000 rows and 10 columns. The commented function debugshow shows the results in the debug window (activate debug mode first).

The results on my computer were: The slow variant fo TrueSplitStr was 2-3 times slower than the SPLITSTR command, the fast variant about 50% slower.

Code (glbasic) Select

LOCAL text$[]
LOCAL split$[]
LOCAL line$

// read a sample csv file
OPENFILE(1, "example.csv", TRUE)
WHILE ENDOFFILE(1) = FALSE
READLINE 1, line$
DIMPUSH text$[], line$
WEND

LOCAL time_start, time_end
LOCAL time_splitstr, time_splitstrslow, time_splitstrfast
LOCAL i, total, count

total = LEN(text$[])

// first test with original SPLITSTR command
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = SPLITSTR(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstr = time_end - time_start

// now the "slow" variant of TrueSplitStr
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = TrueSplitStr_Slow(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstrslow = time_end - time_start

// and finally the "fast" variant of TrueSplitStr
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = TrueSplitStr_Fast(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstrfast = time_end - time_start

PRINT "Original SPLITSTR command     :" + time_splitstr, 0, 0
PRINT "Slow TrueSplitStr function    :" + time_splitstrslow, 0, 30
PRINT "Fast TrueSplitStr function    :" + time_splitstrfast, 0, 60
SHOWSCREEN
KEYWAIT


FUNCTION TrueSplitStr_Slow: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]

l_len_text = LEN(p_text$)

DIM p_split$[0]                           // initialize p_spit$[]

FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
DIMPUSH p_split$[], l_item$       // yes then continue with the next word
l_item$ = ""
ENDIF
NEXT
DIMPUSH p_split$[], l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


FUNCTION TrueSplitStr_Fast: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]
LOCAL l_count                             // number of entries needed for p_split$[]
LOCAL l_idx                               // current index of p_split$[]

l_len_text = LEN(p_text$)

// first get the needed number of entries for p_split$[]
FOR l_ctext = 0 TO l_len_text - 1
IF INSTR(p_delim$, MID$(p_text$, l_ctext, 1)) > -1
INC l_count, 1
ENDIF
NEXT
REDIM p_split$[l_count + 1]

// now do the split
FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
p_split$[l_idx] = l_item$         // yes then continue with the next word
l_item$ = ""
INC l_idx, 1
ENDIF
NEXT
p_split$[l_idx] = l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


FUNCTION debugshow: p_split$[]
LOCAL i
FOR i = 0 TO LEN(p_split$[]) - 1
DEBUG p_split$[i] + "\t"
NEXT
DEBUG "\n"
ENDFUNCTION



[attachment deleted by admin]
#34
inspiriert durch die Frage von Stu_C nach der Arbeitsweise von SPLITSTR

http://www.glbasic.com/forum/index.php?topic=2384.0

habe ich mich an eine modifizierte Version von SPLITSTR gewagt, die doppelte Trennzeichen nicht ignoriert, sondern auch in den Splitprozess miteinbezieht. Wie AndyH richtig bemerkte, kann das z.B. für die Verarbeitung von CSV-Dateien sinnvoll sein.

Eine erste Version von TrueSplitStr, wie ich die Funktion anmaßenderweise mal genannt habe, sieht folgendermaßen aus:

Code (glbasic) Select

FUNCTION TrueSplitStr: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]

l_len_text = LEN(p_text$)

DIM p_split$[0]                           // initialize p_spit$[]

FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
DIMPUSH p_split$[], l_item$       // yes then continue with the next word
l_item$ = ""
ENDIF
NEXT
DIMPUSH p_split$[], l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


Die Bedenken zur Performance, die im obigen Thread auch genannt wurden, sind nicht ganz unberechtigt. Diese Version von SplitStr benötigt etwa 2-3 mal so viel Zeit wie das Original. Als leichte Bremse erweist sich der DIMPUSH-Befehl, der je nach Anzahl der Array-Elemente immer mal wieder neuen Speicher reservieren und dabei auch die bisherigen Array-Elemente erhalten (sprich umschaufeln) muss.

Wenn man jedoch im Vorfeld die benötigte Anzahl der Arrayelemente schon berechnet, kann man das Array mit einem einzigen DIM-Befehl erzeugen und dann innerhalb der FOR-NEXT-Schleife füllen.

Das Coding ist zwar etwas größer, aber auch deutlich schneller als die erste Version:

Code (glbasic) Select

FUNCTION TrueSplitStr_Fast: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]
LOCAL l_count                             // number of entries needed for p_split$[]
LOCAL l_idx                               // current index of p_split$[]

l_len_text = LEN(p_text$)

// first get the needed number of entries for p_split$[]
FOR l_ctext = 0 TO l_len_text - 1
IF INSTR(p_delim$, MID$(p_text$, l_ctext, 1)) > -1
INC l_count, 1
ENDIF
NEXT
REDIM p_split$[l_count + 1]

// now do the split
FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
p_split$[l_idx] = l_item$         // yes then continue with the next word
l_item$ = ""
INC l_idx, 1
ENDIF
NEXT
p_split$[l_idx] = l_item$ 

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


Hier wird zunächst die erforderliche Größe des Arrays errechnet und anschließend das erzeugte Array gefüllt.

Das folgende Coding stellt alle drei Versionen von SplitStr, also

- SPLITSTR - Befehl von GLBasic
- langsame Version von TrueSplitStr
- schnelle Version von TrueSplitStr

gegenüber und gibt die verbrauchten Zeiten an. Als Beispiel habe ich eine CSV-Datei beigefügt, die aus 1000 Zeilen mal 10 Spalten besteht. Die hier auskommentierte Funktion debugshow kann bei aktiviertem Debug-Modus dazu genutzt werden, die Ergebnisse im Debug-Fenster auszugeben.

Die Ergebnisse bei mir waren in etwa so, daß die langsame Variante von TrueSplitStr etwa 2-3 mal langsamer war als der SPLITSTR-Befehl, die schnellere Variante etwa 50% langsamer.

Code (glbasic) Select



LOCAL text$[]
LOCAL split$[]
LOCAL line$

// read a sample csv file
OPENFILE(1, "example.txt", TRUE)
WHILE ENDOFFILE(1) = FALSE
READLINE 1, line$
DIMPUSH text$[], line$
WEND

LOCAL time_start, time_end
LOCAL time_splitstr, time_splitstrslow, time_splitstrfast
LOCAL i, total, count

total = LEN(text$[])

// first test with original SPLITSTR command
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = SPLITSTR(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstr = time_end - time_start

// now the "slow" variant of TrueSplitStr
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = TrueSplitStr_Slow(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstrslow = time_end - time_start

// and finally the "fast" variant of TrueSplitStr
time_start = GETTIMERALL()
FOR i = 0 TO total - 1
count = TrueSplitStr_Fast(text$[i], split$[], ",")
//debugshow(split$[])                     // activate debug mode to see the results in debug window
NEXT
time_end = GETTIMERALL()
time_splitstrfast = time_end - time_start

PRINT "Original SPLITSTR command     :" + time_splitstr, 0, 0
PRINT "Slow TrueSplitStr function    :" + time_splitstrslow, 0, 30
PRINT "Fast TrueSplitStr function    :" + time_splitstrfast, 0, 60
SHOWSCREEN
KEYWAIT


FUNCTION TrueSplitStr_Slow: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]

l_len_text = LEN(p_text$)

DIM p_split$[0]                           // initialize p_spit$[]

FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
DIMPUSH p_split$[], l_item$       // yes then continue with the next word
l_item$ = ""
ENDIF
NEXT
DIMPUSH p_split$[], l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


FUNCTION TrueSplitStr_Fast: p_text$, p_split$[], p_delim$

LOCAL l_len_text                          // length of text$
LOCAL l_ctext                             // loop counter
LOCAL l_char$                             // one character of text$
LOCAL l_item$                             // to add to p_split$[]
LOCAL l_count                             // number of entries needed for p_split$[]
LOCAL l_idx                               // current index of p_split$[]

l_len_text = LEN(p_text$)

// first get the needed number of entries for p_split$[]
FOR l_ctext = 0 TO l_len_text - 1
IF INSTR(p_delim$, MID$(p_text$, l_ctext, 1)) > -1
INC l_count, 1
ENDIF
NEXT
REDIM p_split$[l_count + 1]

// now do the split
FOR l_ctext = 0 TO l_len_text - 1
l_char$ = MID$(p_text$, l_ctext, 1)   // get the next character of text$
IF INSTR(p_delim$, l_char$) = -1      // is it a delimiter?
l_item$ = l_item$ + l_char$       // no, so add it to the next item
ELSE
p_split$[l_idx] = l_item$         // yes then continue with the next word
l_item$ = ""
INC l_idx, 1
ENDIF
NEXT
p_split$[l_idx] = l_item$

RETURN LEN(p_split$[])                    // return the number of strings

ENDFUNCTION


FUNCTION debugshow: p_split$[]
LOCAL i
FOR i = 0 TO LEN(p_split$[]) - 1
DEBUG p_split$[i] + "\t"
NEXT
DEBUG "\n"
ENDFUNCTION



[attachment deleted by admin]
#35
kann manchmal nützlich sein, wenn man mit Bits rumspielt
im optionalen Parameter p_bytes kann man mitgeben, wie groß die zurückgelieferte Binärzahl ist 1 Byte = 8 Zeichen, 2 Byte = 16 Zeichen etc. Voreinstellung ist 4 Byte für 32 Bit-Zahlen

Code (glbasic) Select

PRINT Bin$(2594585588), 0, 0
SHOWSCREEN
KEYWAIT

// ------------------------------------------------------------- //
// ---  BIN$  ---
// paramerters: p_num   number to convert in binary format
//               p_bytes number of bytes, on 32 bit CPU 4 byte
//                      is maximum
// ------------------------------------------------------------- //
FUNCTION Bin$: p_num, p_bytes = 4
   
   LOCAL l_length = p_bytes * 8
   LOCAL l_counter
   LOCAL l_result$
   
   FOR l_counter = l_length - 1 TO 0 STEP -1
      IF bAND(p_num,POW(2, l_counter))
         l_result$ = l_result$ + "1"
      ELSE
         l_result$ = l_result$ + "0"
      ENDIF
   NEXT
   
   RETURN l_result$

ENDFUNCTION // BIN$
#36
erstaunlich, was man im Netz alles so findet, hier ein komplettes Buch über GLBasic.

http://www.terpsichori-ev.de/GLBasic.pdf

Autor hab ich jetzt auf die Schnelle nicht herausgefunden (Gernot daselbst?). Die Homepage befaßt sich eigentlich mit traditionellen griechischen Tänzen.

K.A. warum man so ein tolles Werk geheimhält ;) ... oder ist es etwa schon bekannt?
#37
(If you find extreme errors in my poor english please let me know)

Surely to compare this with Stephen Hawkings book about the time is overstated, but in my borderless modesty I couldn't catch something more adequated. ;)

As Gernot told us, after some questions about object orientated enhancements for GLBasic, that he wouldn't implement them and who really need this, should do it with INLINE, I took this words to my heart and tried to realize some OOP in GLBasic. One thing before we start: I'm not an expert in C++ and object orientated programming. So if you see some big violations against C++ or OOP dogmas, please tell it to me in a gentle way.

In principle GLBasic already works with classes. For example if you compile the following coding:


TYPE t_anything
   x
   y
ENDTYPE

LOCAL something AS t_anything


GLBasic will generate this C++ code.
(you'll find it in C:\Documents and Settings\<computername>\Local Settings\Temp\glbasic\gc_temp_class.h, please replace <computername> with your own one)


class t_anything
{
public:
   DGInt x;
   DGInt y;
   t_anything()
   {
      x = 0;
      y = 0;
   }
   t_anything(const t_anything& _in_)
   {*this = _in_;}
   t_anything& operator=(const t_anything& _in_)
   {
      this->x = _in_.x;
      this->y = _in_.y;
   return *this;
   }

};


A class was created from the GLBasic TYPE with the attributes x and y, two constructors to initialize the data and an overloaded = operator. The = operator must be overloaded to allow assignments like


LOCAL something AS t_anything
LOCAL something_new AS t_anything

something.x = 5
something.y = 7
something_new = something


The object creation of the class is done in the main file gpc_temp0.cpp within the function __MainGameSub__. The command is "REGISTER_VAR(t_anything, something);"
In principle all attributes are defined as "public". In order to change this we can define our own classes with INLINE.


So now we have the pattern to implement own classes. The following small example creates a class and plays something with the attributes.

Code (glbasic) Select

INLINE
// class definition
class canything
{
private:
DGInt x;
DGInt y;

public:
// standard constructor
canything()
{
x = 0;
y = 0;
}

DGInt getx() { return x; }
DGInt gety() { return y; }
void setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}

};
ENDINLINE

INLINE
// create an object
canything something;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
//set x and y accidently
something.setpos(RND(scrx), RND(scry));
x = something.getx();
y = something.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND


Ok this works. Of course it is not very sensible.
In this example the functions (or methods) are defined within the class definition. If doing so, the class methods are defind implicit as inline methods (as far as I know). This is no handicap for this example. But if a method is bigger and is called several times in the coding it could bloat the final coding. We can try to change our class definition, so that within the class only the method headers are shown and the implementation outside ...

Code (glbasic) Select

INLINE
// class definition
class canything
{
private:
DGInt x;
DGInt y;

public:
canything();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

canything::canything()
{
x = 0;
y = 0;
}

DGInt canything::getx() { return x; }
DGInt canything::gety() { return y; }
void canything::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE


... and we're facing a bad surprise. The compiler tells us several error messages, which are not very understandable on the fist view. On the second view (in my case the second view came from Gernot) the reason for the error messages are more clear. The implementation of the class methods was done within the main function of the coding, which is generated by GLBasic, more detailed, within the function __MainGameSub__, which is called from the WinMain function. Thus the class methods are implemented within a function and I don't know a compiler which allows that.

So I'll have to find another way to make the class definition global. You'll find several examples in the forum on how to do this.


// empty function in order to end the main function
FUNCTION __dummy:
ENDFUNCTION

INLINE
// here we can define our class
ENDINLINE


Now we're facing the next problem. Now the class is outside any function, but anyhow it isn't known within the main function. It's because the class def. is set AFTER the main function within the C++ coding which is generated by GLBasic. We can manage this by  creating our own main function, which is defined after INLINE. Schematically our coding looks as follows:


// call our own main function
MyMain()

// end original main function here
FUNCTION __dummy:
ENDFUNCTION

// class definition and implementation
INLINE
class...
ENDINLINE

// own main function
FUNCTION MyMain:
   ...
ENDFUNCTION



Once more the complete coding:

Code (glbasic) Select

// calling own main function
MyMain()


FUNCTION __dummy:
ENDFUNCTION

INLINE
// class definition
class canything
{
private:
DGInt x;
DGInt y;

public:
canything();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

// Implementation of the class methods
canything::canything()
{
x = 0;
y = 0;
}

DGInt canything::getx() { return x; }
DGInt canything::gety() { return y; }
void canything::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

INLINE
// create an object
canything something;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
// set x and y accidently
something.setpos(RND(scrx), RND(scry));
x = something.getx();
y = something.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND

ENDFUNCTION // MYMAIN


    Fine. Now we have the knowledge for further mistakes. I mean arrays. Often not only one variable of a TYPE (respectively an object) is needed but an array. As we're programming in C++ now there will be several ways to realize that.


    • with C++ arrays (canything something[20]; )
    • with linked lists
    • with an own array class
    • or ... we are clever an are using the functionallity of GLBasic

We will extend the example above by creating an array of class canything. This can be done very simple with the command


DGArray<canything> arr_something;


as described in the GLBasic help fle, DGArray is a template class. Thus we'll have to tell it which kind of array we want (here <canything>). With this array we can use all array functions of GLBasic like DIM, REDIM, DIMPUSH and DIMDEL.

Now here the changed main function:

Code (glbasic) Select

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

LOCAL i

INLINE
// create an object
canything something;
DGArray<canything> arr_something;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

// create 10 elements
FOR i = 0 TO 9
INLINE
something.setpos(RND(scrx), RND(scry));
DIMPUSH(arr_something, something);
ENDINLINE
NEXT

WHILE TRUE
INLINE
// display array in a FOREACH loop
for(i = 0; i <= LEN(arr_something()) - 1; )
{
canything& a = arr_something(i++);
x = a.getx();
y = a.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
}
SHOWSCREEN();
ENDINLINE
WEND

ENDFUNCTION // MYMAIN


One word to the FOREACH loop within WILE-WEND. I've "learned" this by looking on how GLBasic is translating a FOREACH loop in C++ coding. You see that the loop counter "i" is not incremented in the for statement but later within the loop. This is important. Otherwise the loop will start with the second element and will cause an error when trying to process the last element which is out of bounds of the array.

The command


canything& a = arr_something(i++);


give us the address of the current array element in the varialbe a, so we can call all class methods with it. The DELETE statement of GLBasic will be translated to the commands


DIMDEL(arr_something, --i);
continue;


Ok, enough for now.

Result:
It made a lot of fun working with INLINE and classes. If it make sense ... I really don't know. Ok we'll have encapsulation of the data but there are so many traps when programming in C++ and it makes much more effort working in that way comparing the programming with GLBasic only. Anyway at the moment I enjoy playing with it.

#38
Der Vergleich mit Stephen Hawkings Büchlein über die Zeit ist natürlich stark übertrieben, aber in meiner grenzenlosen Bescheidenheit ist mir nichts passenderes eingefallen ;).

Nachdem Gernot diversen Anfragen zu objektorientierten Erweiterungen in GLBasic mehr oder minder ein Abfuhr erteilt hat und er meinte, wer es unbedingt haben wolle, solle sich mit INLINE etwas basteln, habe ich mir diesen Rat zu Herzen genommen und probiert, genau das in GLBasic umzusetzen. Eines gleich vorweg: Ich bin alles andere als fit in Sachen C++ und Objektorientierung. Wer also in den kommenden Ausführungen grobe Verstöße gegen C++ - oder OOP-Dogmen erkennt, darf mir das schonend beibringen.

Im Prinzip arbeitet GLBasic schon mit Klassen und Objekten. Wenn man z.B. folgendens Coding kompiliert,


TYPE t_irgendwas
   x
   y
ENDTYPE

LOCAL etwas AS t_irgendwas


wird daraus dieses C++ - Coding generiert:
(zu finden unter C:\Dokumente und Einstellungen\<Rechnername>\Lokale Einstellungen\Temp\glbasic\gpc_temp_class.h, <Rechnername> bitte durch eigenen Rechnernamen ersetzen)


class t_irgendwas
{
public:
   DGInt x;
   DGInt y;
   t_irgendwas()
   {
      x = 0;
      y = 0;
   }
   t_irgendwas(const t_irgendwas& _in_)
   {*this = _in_;}
   t_irgendwas& operator=(const t_irgendwas& _in_)
   {
      this->x = _in_.x;
      this->y = _in_.y;
   return *this;
   }

};


Aus unserem TYPE ist also eine Klasse geworden mit den beiden Attributen x und y, zwei Konstruktoren sowie einem überladenen = - Operator. Der = - Operator muss deshalb überladen werden, damit Zuweisungen wie


LOCAL etwas AS t_irgendwas
LOCAL nochwas AS t_irgendwas

etwas.x = 5
etwas.y = 7
nochwas = etwas


möglich sind. Die Instanzierung der Klasse erfolgt im Hauptprogramm gpc_temp0.cpp innerhalb der Funktion __MainGameSub__. Der Befehl lautet "REGISTER_VAR(t_irgendwas, etwas);"
Alle Attribute der Klasse sind prinzipiell als "public" definiert. Und genau das kann mit der Formulierung von eigenen Klassen mittels INLINE anders gestaltet werden.


Somit haben wir auch schon prinzipiell die Vorlage für die Implementierung von eigenen Klassen. Folgendes kleine Beispiel erstellt eine Klasse mit einigen Methoden, spielt ein wenig damit rum.

Code (glbasic) Select

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
// Standardkonstruktor
cirgendwas()
{
x = 0;
y = 0;
}

DGInt getx() { return x; }
DGInt gety() { return y; }
void setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}

};
ENDINLINE

INLINE
// ein Objekt anlegen
cirgendwas etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
//x und y zufällig setzen
etwas.setpos(RND(scrx), RND(scry));
x = etwas.getx();
y = etwas.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND


Läuft schon mal, wenn es auch wenig sinnvoll ist.
Im Beispiel sind die Funktionen innerhalb der Klassendefinitionen ausformuliert. Wenn man das so macht, sind diese Klassenmethoden implizit als inline deklariert (soweit ich richtig informiert bin). In diesem Beispiel wäre das sicherlich kein Schaden. Wenn eine Methode jedoch etwas umfangreicher ist und an vielen Stellen im Coding aufgerufen wird, kann dies den Code aufblähen. Also probieren wir doch mal, unsere Klassendefinition so umzustellen, daß in der Klasse nur die Methoden definiert werden und das eigentliche Coding außerhalb ...

Code (glbasic) Select

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
cirgendwas();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

cirgendwas::cirgendwas()
{
x = 0;
y = 0;
}

DGInt cirgendwas::getx() { return x; }
DGInt cirgendwas::gety() { return y; }
void cirgendwas::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE


... und erleben eine böse Überraschung. Der Compiler überhäuft uns mit freundlichen, auf den ersten Blick unverständlichen Fehlermeldungen. Auf den zweiten Blick (in meinem Fall kam der zweite Blick von Gernot) wird dann auch klar, warum die Fehlermeldungen kommen. Die Stelle, an der ich die Klassendefinition vorgenommen habe, liegen im von GLBasic erzeugten C++ - Programm innerhalb der Main-Funktion, genauer innerhalb der Funkton __MainGameSub__, die von der WinMain-Funktion aufgerufen wird. Dadurch, daß ich die Methoden außerhalb der Klassendefinition gesetzt habe, werden diese innerhalb einer Funktion __MainGameSub__ geschrieben. Funktionsdefinitionen innerhalb einer Funktion sieht kaum ein Compiler gern.

Folglich muss ich eine Möglichkeit finden, die Klassendefinition global bekannt zu machen. Wie man das im Programm darstellt, wurde ja schon an vielen Stellen im Forum beschrieben.


// leere Funktion, damit das folgende Coding außerhalb der Main-Funktion liegt
FUNCTION __dummy:
ENDFUNCTION

INLINE
// ab hier kann dann die Klassendefintion + Implementierung liegen
ENDINLINE


Jetzt kommt das nächste Problem. Die Klasse liegt zwar jetzt außerhalb einer Funktion, in der Main-Funktion ist sie jedoch nicht bekannt, weil sie im generierten Coding NACH der Main-Funktion liegt. Man kann sich damit behelfen, daß man sich seine eigene "Main-Funktion" bastelt, die nach dem INLINE-Block liegt. Schematisch ist das Programm dann folgendermaßen aufgebaut.


// Aufruf der eigenen Main-Funktion
MyMain()

// Main beenden mit Dummy-Funktion
FUNCTION __dummy:
ENDFUNCTION

// Klassendefinition
INLINE
class ...
ENDINLINE

// eigene Main-Funktion
FUNCTION MyMain:
...
ENDFUNCTION



Hier noch mal das komplette Coding:

Code (glbasic) Select

// Aufruf der eigenen Main-Funktion
MyMain()


FUNCTION __dummy:
ENDFUNCTION

INLINE
// Klassendefinition
class cirgendwas
{
private:
DGInt x;
DGInt y;

public:
cirgendwas();
DGInt getx();
DGInt gety();
void setpos(const DGInt new_x, const DGInt new_y);
};

// Implementierung der Klassenmethoden
cirgendwas::cirgendwas()
{
x = 0;
y = 0;
}

DGInt cirgendwas::getx() { return x; }
DGInt cirgendwas::gety() { return y; }
void cirgendwas::setpos(const DGInt new_x, const DGInt new_y)
{
x = new_x;
y = new_y;
}
ENDINLINE

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

INLINE
// ein Objekt anlegen
cirgendwas etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

WHILE TRUE
INLINE
//x und y zufällig setzen
etwas.setpos(RND(scrx), RND(scry));
x = etwas.getx();
y = etwas.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
SHOWSCREEN();
SLEEP(1500);
ENDINLINE
WEND

ENDFUNCTION // MYMAIN



Fein, jetzt haben wir das Rüstzeug für weitere Untaten. Und zwar Array's. Häufig will man ja nicht nur eine Variable eines TYPE's (bzw. eines Objektes) haben, sondern gleich ein ganzes Array davon. Da wir uns jetzt sowieso schon in C++ bewegen, stehen einem diverse Möglichkeiten zur Verfügung


  • man ertellt sich C++ - Arrays (cirgendwas etwas[20]; )
  • man nutzt verkette Listen
  • man schreibt sich eine eigene Array-Klasse
  • oder ... man ist ganz clever und nutzt die Funktionalität von GLBasic

Wir erweitern obiges Beispiel, indem wir ein Array der Klasse cirgendwas anlegen. Dies kann "ganz einfach" mit der Anweisung


DGArray<cirgendwas> arr_etwas;


geschehen. Wie in der GLBasic-Hilfe beschrieben, ist DGArray eine Template-Klasse. Diesem müssen wir natürlich mitteilen, von welchem Typ wir eine Klasse wünschen (<cirgendwas>). Auf dieses Array kann man jetzt alle bekannten GLBasic-Befehle wie DIM, REDIM, DIMPUSH, DIMDEL anwenden.

Hier nur die geänderte MyMain-Funktion:

Code (glbasic) Select

// ------------------------------------------------------------- //
// ---  MYMAIN  ---
// ------------------------------------------------------------- //
FUNCTION MyMain:

LOCAL i

INLINE
// ein Objekt anlegen
cirgendwas etwas;
DGArray<cirgendwas> arr_etwas;
ENDINLINE

LOCAL scrx, scry, x, y
GETSCREENSIZE scrx, scry

// einfach mal 10 Elemente erzeugen
FOR i = 0 TO 9
INLINE
etwas.setpos(RND(scrx), RND(scry));
DIMPUSH(arr_etwas, etwas);
ENDINLINE
NEXT

WHILE TRUE
INLINE
// Array ausgeben mit der "FOREACH-Schleife"
for(i = 0; i <= LEN(arr_etwas()) - 1; )
{
cirgendwas& a = arr_etwas(i++);
x = a.getx();
y = a.gety();
PRINT(CGStr("X:") + x + CGStr(" - Y:") + y, x, y);
}
SHOWSCREEN();
ENDINLINE
WEND

ENDFUNCTION // MYMAIN


Noch ein Wort zur "FOREACH-Schleife" innerhalb von WHILE-WEND. Dieses Konstrukt habe ich abgekupfert, indem ich nachgesehen habe, wie GLBasic eine FOREACH-Schleife in C++ - Code übersetzt. Es fällt auf, daß der Schleifenzähler "i" nicht in der FOR-Anweisung hochgezäht wird, sondern erst innerhalb der Schleife. Das ist wichtig, da sonst die Schleife mit dem zweiten Element beginnen würde und beim letzten Durchlauf auf einen Fehler läuft, da man auf ein nicht existierendes Element des Arrays zugreifen wollte.

Die Anweisung


cirgendwas& a = arr_etwas(i++);


liefert in der lokalen Variablen a die Adresse auf das aktuelle Array-Element. Mit diesem können wir dann wieder ganz normal mit den Klassen-Methoden arbeiten. Das Löschen von Array-Elementen mit DELETE wird übersetzt in die Anweisungen


DIMDEL(arr_etwas, --i);
continue;


In der GLBasic-Hilfe zu INLINE ist beschrieben, daß bei der Verwendung von DGArray die Operatoren "=" und "()" definiert werden müssen. Das obige Beispiel oder auch das von Schranzor (http://www.glbasic.com/forum/index.php?topic=2224.0) sind aber auch ohne überladene Opertoren lauffähig. In welchen Fällen das dann wirklich erforderlich ist, entzieht sich leider meiner Kenntnis.

Jetzt erst einmal genug davon.

Fazit:
Das Rumgefummel mit Klassen und INLINE macht richtig Spaß. Was die Sinnhaftigkeit angeht, bin ich jedoch arg im Zweifel. Was erreicht man durch die ganze Plackerei, die einem die ungezählten Fall-Stricke der C++ - Programmierung in den Weg legen. Datenkapselung. Schön und gut. Ob das jedoch den Aufwand rechtfertigt, kann ich nicht abschließend beantworten. Zur Zeit macht es einfach Spaß, damit rumzuspielen.

P.S.
Wenn Schranzor mich jetzt dazu verdonnert, diesen ganzen Wirsing ins englische zu übersetzen, ist mein Urlaub ganz versaut ;)


#39
ich spiele gerade ein wenig mit INLINE und Klassendefinitionen herum. Das klappt soweit ganz gut, nur wenn ich Klassen in Verbindung mit DGArray verwenden will, habe ich noch einige Probleme, weil die Klassendefinition dann innerhalb von __MainGameSub_ lokal angelegt werden.

Der einzige Weg, wie ich es bisher zu einer fehlerfreien Kompilierung bringen konte, ist, die Klassendefinitionen in eine Header-Datei zu packen und diese z.B. in glb.h einzubinden. Kennt jemand vielleicht noch eine andere Möglichkeit, includes VOR __MainGameSub_ einzubinden?

#40
also ehrlich, ich finds nicht gut, wenn hier Posts einfach gelöscht werden. Man kann etwas klarstellen oder auch korrigieren, aber einfach streichen stinkt nach willkürlicher Zensur, und dabei ist's mir erst mal wurscht, ob das aus Sicht der Mods gerechtfertigt scheint oder auch nicht.

aber zum Thema:
Ich finde die Idee von Schranzi mit der Timer-Funktion gar nicht so verkehrt, vor allem wenn man damit mehrere Timer verwalten könnte. Ich habs damit mal versucht, wobei man sogar ohne GLOBALs auskommen kann ;). Dazu müssten die Timer aber erst einmal initialisiert werden. Das geht, indem man als Zeitdauer (millisec) einen negativen Wert mitgibt. Die Timernummern sollten bei Null starten

Code (glbasic) Select

DeltaTimer(0, -1)
DeltaTimer(1, -1)
DeltaTimer(2, -1)

LOCAL counter1, counter2, counter3

WHILE TRUE
IF DeltaTimer(0, 100) THEN INC counter1, 1
IF DeltaTimer(1, 500) THEN INC counter2, 1
IF DeltaTimer(2, 1000) THEN INC counter3, 1
PRINT "1. Timer: " + counter1, 0, 0
PRINT "2. Timer: " + counter2, 0, 20
PRINT "3. Timer: " + counter3, 0, 40
SHOWSCREEN
WEND


// ------------------------------------------------------------- //
// ---  DeltaTimer  ---
// num: internal number of the timer
// millisec: duration ot timer interval
// millisec < 0 --> new timer is created
// ------------------------------------------------------------- //
FUNCTION DeltaTimer: num, millisec

STATIC timer[]

IF millisec <= 0
DIMPUSH timer[], num
timer[LEN(timer[])-1] = GETTIMERALL()
ELSE
IF num > LEN(timer[]) - 1 THEN RETURN FALSE
IF timer[num] < GETTIMERALL()
timer[num] = GETTIMERALL() + millisec
RETURN TRUE
ENDIF
ENDIF
RETURN FALSE

ENDFUNCTION // DELTATIMER