How do I make an event in Delphi happen just once? - delphi

I'm a starter programmer and I'm trying to make a text-based RPG game (Like Zork) using Delphi Pascal language.
I made an event in which the main character open a chest and find some items:
begin
text1.text := 'You see a chest. It is unlocked.';
end;
if edit1.Text = 'Open Chest' then
text1.Text := 'You found 50 Gold Pieces, a Short Sword, a Cloth Armor and a Satchel Bag.';
end;
But I want to make it in a way that, whenever someone opens the chest AFTER the first time, the chest will be empty, since the player already took the items.
In other words, when someone type 'Open Chest' in the TEdit for the second time, it says something like "It is empty."
But how?

You have to use additional variable that will tell you whether chest has already been opened or not.
var
ChestOpened: boolean;
// initialize at beginning
ChestOpened := false;
...
if Edit1.text = 'Open Chest' then
begin
if ChestOpened then
Text1.Text := 'Chest is empty'
else
begin
ChestOpened := true;
Text1.Text := 'You found 50 Gold Pieces, a Short Sword, a Cloth Armor and a Satchel Bag.'
end;
end;

Related

Search and Replace in a Trichedit

We are working with XE7. We are trying to build a search and replace feature into a TRichEdit.
We would like it to work just like it works in the Delphi compiler. We display our own search and replace dialog, we are not using the Delphi replace dialog.
We display the dialog, and when the user clicks the Replace button, the dialog closes, and for each instance found we want to popup a message dialog asking the user whether they want to replace that occurrence.
When we do this, the user cannot see what is selected, and if they click Yes on the message dialog, nothing is being replaced.
Our code is below:
procedure Tviewform.select_text(fndpos:integer);
begin
setfocus;
asciieditor.selstart:=fndpos;
asciieditor.sellength:=length(vwvars[curviewwin]^.finddata);
end;
procedure Tviewform.search_and_replace;
var position,endpos,fndans,cont:integer; foptions:tsearchtypes; msgques,stopall:word;
begin
with asciieditor do
begin
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
foptions:=[];
stopall:=0;
cont:=0;
if findinfo.onbut.checked then foptions:=foptions+[stMatchCase];
lines.beginupdate;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
while (fndans<>-1) and (stopall=0) do
begin
select_text(fndans);
msgques:=mainform.silang1.messagedlg('Replace with '+vwvars[curviewwin]^.replace+'?.',mtinformation,[mbyes,mbno,mbcancel],0);
if msgques=mryes then
begin
asciieditor.clearselection;
seltext:=vwvars[curviewwin]^.replace;
end else
begin
if msgques=mrcancel then
begin
cont:=0;
stopall:=1;
end else cont:=1;
end;
asciieditor.Repaint;
if cont=1 then
begin
inc(vwvars[curviewwin]^.sstartpos,length(vwvars[curviewwin]^.finddata));
endpos:=length(asciieditor.text)-vwvars[curviewwin]^.sstartpos;
fndans:=findtext(vwvars[curviewwin]^.finddata,vwvars[curviewwin]^.sstartpos,endpos,foptions);
end;
end;
lines.endupdate;
end;
end;
I see a number of issues with your code:
You are moving input focus away from the TRichEdit when you call select_text() and MessageDlg(), so make sure that TRichEdit.HideSelection is set to False (it is True by default) in order to actually see the selection.
You are calling BeginUpdate() on the TRichEdit, which disables screen redraws for it, including Repaint().
You don't need to call TRichEdit.ClearSelection() before setting the TRichEdit.SelText.
When updating vwvars[curviewwin]^.sstartpos after each replacement, you need to reset it to fndAns (the current selection's position) plus the length of vwvars[curviewwin]^.replace (the new text), not the length of vwvars[curviewwin]^.finddata (the old text). You are not moving it beyond the new text, so you are searching older text over and over.
If the user chooses No in the MessageDlg(), you are not updating vwvars[curviewwin]^.sstartpos to move past the text that the user didn't want to replace.
When setting endpos, length(asciieditor.text) can be asciieditor.gettextlen() instead for better efficiency. But more importantly, you should not be subtracting vwvars[curviewwin]^.sstartpos from endpos at all. By doing that, you are skipping more and more text at the end of the TRichEdit from the search with each replacement made. In fact, you should be using just the asciieditor's current text length as the end position of each search, so you don't actually need endpos at all.
Depending on the size of your TRichEdit and its content, if it has scrollbars enabled, you should send the TRichEdit a EM_SCROLLCARET message each time you find a matching text and before you prompt the user with the MessageDlg(), in case the matching text is off-screen.
With that said, try something more like this:
procedure Tviewform.search_and_replace;
var
fndAns: Integer;
fOptions: TSearchTypes;
msgQues: Word;
procedure select_text;
begin
{AsciiEditor.}SetFocus;
AsciiEditor.SelStart := fndAns;
AsciiEditor.SelLength := Length(vwvars[curviewwin]^.finddata);
AsciiEditor.Perform(EM_SCROLLCARET, 0, 0);
AsciiEditor.Update;
end;
begin
fOptions := [];
if FindInfo.OnBut.Checked then Include(fOptions, stMatchCase);
repeat
fndAns := AsciiEditor.FindText(vwvars[curviewwin]^.finddata, vwvars[curviewwin]^.sstartpos, AsciiEditor.GetTextLen, fOptions);
if fndAns = -1 then Break;
select_text;
msgQues := MainForm.SiLang1.MessageDlg('Replace with ' + vwvars[curviewwin]^.replace + '?', mtinformation, [mbYes,mbNo,mbCancel], 0);
if msgQues = mrYes then
begin
AsciiEditor.SelText := vwvars[curviewwin]^.replace;
AsciiEditor.Update;
Inc(fndAns, Length(vwvars[curviewwin]^.replace));
end
else if msgQues = mrNo then
begin
Inc(fndAns, Length(vwvars[curviewwin]^.finddata));
end else
Break;
vwvars[curviewwin]^.sstartpos := fndAns;
until False;
end;

Delphi MediaPlayer notification on song stopped

I need to embed a trivial MP3 player in a Delphi7 application.
I will simply scan a directory and play all the files in a random order.
I found two possible solutions: one using the Delphi MediaPlayer, and one using the PlaySound Windows API.
None are working.
The problem seems to be in the missing "stop" notification.
Using PlaySound like this:
playsound(pchar(mp3[r].name), 0, SND_ASYNC or SND_FILENAME);
I could not find a way to (politely) ask Windows to inform me when a song stopped playing.
Using the Delphi MediaPlayer, internet is full of suggestions copy/pasted one from the other, like here:
http://www.swissdelphicenter.ch/en/showcode.php?id=689
http://delphi.cjcsoft.net/viewthread.php?tid=44448
procedure TForm1.FormCreate(Sender: TObject);
begin
MediaPlayer1.Notify := True;
MediaPlayer1.OnNotify := NotifyProc;
end;
procedure TForm1.NotifyProc(Sender: TObject);
begin
with Sender as TMediaPlayer do
begin
case Mode of
mpStopped: {do something here};
end;
//must set to true to enable next-time notification
Notify := True;
end;
end;
{
NOTE that the Notify property resets back to False when a
notify event is triggered, so inorder for you to recieve
further notify events, you have to set it back to True as in the code.
for the MODES available, see the helpfile for MediaPlayer.Mode;
}
My problem is that I do get a NotifyValue == nvSuccessfull when a song is over but ALSO when I start a song, so I cannot rely on it.
Furthermore, I never, ever receive a change in state of the "mode" property, that should become mpStopped according to all the examples I found.
There is a similar question here
How can I repeat a song?
but it does not work because, as said, I receive the nvSuccessfull twice, without a way to distinguish between the start and the stop.
Last but not least, this app should work from XP to Win10, that's why I am developing with Delphi7 on WinXP.
Thank you and sorry for the length of this post, but I really tried many solutions before asking for help.
To detect when to load a new file for playing, you can use the OnNotify event and the EndPos and Position properties of the TMediaPlayer (hereafter called MP)
First setup the MP and select a TimeFormat, for example
MediaPlayer1.Wait := False;
MediaPlayer1.Notify := True;
MediaPlayer1.TimeFormat := tfFrames;
MediaPlayer1.OnNotify := NotifyProc;
When you load a file for playing, set the EndPos property
MediaPlayer1.FileName := OpenDialog1.Files[NextMedia];
MediaPlayer1.Open;
MediaPlayer1.EndPos := MediaPlayer1.Length;
MediaPlayer1.Play;
And the OnNotify() procedure
procedure TForm1.NotifyProc(Sender: TObject);
var
mp: TMediaPlayer;
begin
mp:= Sender as TMediaPlayer;
if not (mp.NotifyValue = TMPNotifyValues.nvSuccessful) then Exit;
if mp.Position >= mp.EndPos then
begin
// Select next file to play
NextMedia := (NextMedia + 1) mod OpenDialog1.Files.Count;
mp.FileName := OpenDialog1.Files[NextMedia];
mp.Open;
mp.EndPos := mp.Length;
mp.Position := 0;
mp.Play;
// Set Notify, important
mp.Notify := True;
end;
end;
Finally a comment to your attempt to use the MP.Mode = mpStopped mode to change to a new song. The mode is changed when the buttons are operated, iow mpStopped when the user presses the Stop button. Changing the song and starting to play it would likely not be what the user expected.

How to let Delphi read from certain line to a certain line in a text file

I just started Delphi and I'm not using database right now, and this is just a exercise that I'm doing (text file)
My program's form is setup like this:
And this is what's in my Text file:
Description: If the user wants to view a chess match, they have to enter the Chess match name then the program must looks for the Chess Match name then read everything from that line until it reaches the "-------------------------", then it must display it on to the Rich Edit component
Here is my code:
begin
AssignFile(tFile, 'ChessRecords.txt');
Reset(tFile);
while not Eof(tFile) do
begin
sGameName:= '';
Readln(tFile, sLine);
iPos:= Pos('/', sLine);
sGameName:= Copy(sLine, 1,iPos-1);
if sGameName = edtGameName.Text then
begin
repeat
redOut.Lines.Add(sLine);
until (sLine = '-------------------------');
end;
end;
end;
end.
Your repeat until loop does not read from the file (Readln()) anymore - you also have to do that (and check for Eof() as well). Likewise each time you call the function you can also do an Inc( iLine ), but I don't see a reason why you want to count lines.
Rob Kennedy, MartynA and ZENsan are right: your approach is not up to date, but rock solid to still execute in 20 years.

Universal approach to send virtual key codes with Delphi

I am trying to write international program and need to send some text to "other text edit programs" like word or notepad or a browser. On the other hand I am not sure that I can find an international way(because of the different keyboard layouts)
it would be nice to use a code like below
SendMessage(FindActiveWindowsHWND,WM_SETTEXT,0,Integer(PChar('My String')));
and I dont have function like FindActiveWindowsHWND
Edit: The code I am tried but not satisfied so far;
procedure FindActiveWindowsHWND();
var
ThreadInfo: TGUIThreadInfo;
activewindowsHwnd: HWND;
begin
GetGUIThreadInfo(0,ThreadInfo);
activewindowsHwnd:= ThreadInfo.hwndActive; (or ThreadInfo.hwndFocus);
end;
also I used Sendinput function like this
procedure SendKey(vKey: SmallInt; booDown: boolean);
var
GInput: array[0..0] of tagINPUT; //GENERALINPUT;
// doesn't have to be array :)
begin
GInput[0].Itype := INPUT_KEYBOARD;
GInput[0].ki.wVk := vKey;
GInput[0].ki.wScan := 0;
GInput[0].ki.time := 0;
GInput[0].ki.dwExtraInfo := 0;
if not booDown then
GInput[0].ki.dwFlags := KEYEVENTF_KEYUP
else
GInput[0].ki.dwFlags := 0;
SendInput(1, GInput[0], SizeOf(GInput));
end;
then
SendKey(65,true); //to send an "A" for example
but instead it sent an "a" and when I try to send an "a" using SendKey(97,true) it sent "1".
it is really interesting that I have to send shift key down to write uppercase letters
You can use GetGUIThreadInfo() to get the HWND of the currently focused window in another process. Not all window types accept WM_SETTEXT, though. You could use SendInput() to put Unicode characters into the keyboard queue, though. Or use the Automation API, like David said, though not all window types implement that.

Delphi7, Save User's Changes or other User's Information / Notes

In my program, the user completes a form and then presses Submit. Then, a textfile or a random extension file is created, in which all the user's information is written. So, whenever the user runs the application form, it will check if the file, which has all the information, exists, then it copies the information and pastes it to the form. However, it is not working for some reason (no syntax errors):
procedure TForm1.FormCreate(Sender: TObject);
var
filedest: string;
f: TextFile;
info: array[1..12] of string;
begin
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
if FileExists(filedest) then
begin
AssignFile(f,filedest);
Reset(f);
ReadLn(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
Edit1.Text := info[1];
Edit2.Text := info[2];
ComboBox1.Text := info[3];
ComboBox5.Text := info[4];
ComboBox8.Text := info[4];
ComboBox6.Text := info[5];
ComboBox7.Text := info[6];
Edit3.Text := info[7];
Edit4.Text := info[8];
Edit5.Text := info[11];
Edit6.Text := info[12];
ComboBox9.Text := info[9];
ComboBox10.Text := info[10];
CloseFile(f);
end
else
begin
ShowMessage('File not found');
end;
end;
The file exists, but it shows the message File not found. I don't understand.
I took the liberty of formatting the code for you. Do you see the difference (before, after)? Also, if I were you, I would name the controls better. Instead of Edit1, Edit2, Edit3 etc. you could use eFirstName, eLastName, eEmailAddr, etc. Otherwise it will become a PITA to maintain the code, and you will be likely to confuse e.g. ComboBox7 with ComboBox4.
One concrete problem with your code is this line:
readln(info[1], info[2], info[3], info[4], info[5], info[6], info[7],
info[8], info[9], info[10], info[11], info[12]);
You forgot to specify the file f!
Also, before I formatted your code, the final end of the procedure was missing. Maybe your blocks are incorrect in your actual code, so that ShowMessage will be displayed even if the file exists? (Yet another reason to format your code properly...)
If I encountered this problem and wanted to do some quick debugging, I'd insert
ShowMessage(BoolToStr(FileExists(filedest), true));
Exit;
just after the line
filedest := ...
just to see what the returned value of FileExists(filedest) is. (Of course, you could also set a breakpoint and use the debugger.)
If you get false, you probably wonder what in the world filedest actually contains: Well, replace the 'debugging code' above with this one:
ShowMessage(filedest);
Exit;
Then use Windows Explorer (or better yet: the command prompt) to see if the file really is there or not.
I'd like to mention an another possibility to output a debug message (assuming we do not know how to operate real debugger yet):
{ ... }
filedest := ExtractFilePath(ParamStr(0)) + 'User\Identity\IdentityofMyself.txt';
AllocConsole; // create console window (uses Windows module) - required(!)
WriteLn('"' + filedest + '"'); // and output the value to verify
if FileExists(filedest) then
{ ... }

Resources