I am attempting to write a Hangman game using OOP. I have it done using procedural programming. I am not looking to make it a two person game yet. What Objects and methods would you all use?
I was thinking

newWord = class(tObject)
word : string,
definition : string,
typeOfSpeach : string,
function setWords : wordString;
function setDefination : wordString // need 4 other definations, plus the correct one
function setTypeOfSpeach;

It will be getting words and definitions form a 5000 text file.

I am also having issues with the same letter being able to be pressed, I suspect that once I am using objects that will clear up.

Here is what I have that works as long as I only type a correct letter once..
I also would like to get away from using global variables

unit main;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls,
  Vcl.Imaging.jpeg, strutils;


type
  Thangman = class(TForm)
    Button1: TButton;
    RadioGroup1: TRadioGroup;
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
    procedure RadioGroup1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  aLabel : TLabel;

  hangman: Thangman;

  wordList : tStringList;
  definitionList : tStringList;
  partsOfSpeachList : tStringList;
  wordANDdefinition : tStringList;

  partsOfSpeach : string;
  word : string;
  definition : string;
  pressed : byte;

  lettersGuessed : array of String;


  correctDefinition : string;
  Q : byte;
  J : byte;


implementation

{$R *.dfm}

procedure deleteDashes();
var
  i: Integer;
begin
  hangman.RadioGroup1.Items.Clear;

  for i := hangman.ComponentCount - 1 downto 0 do
  begin
    if hangman.Components[i] is tLabel then
    begin
      hangman.Components[i].Free;
    end;
  end;
end;

procedure createDashes();
var
//  q : byte;
  x : byte;
//  xx : byte;
begin
//  xx := 0;
  for X := 1 to length(word) do
  begin
    aLabel := TLabel.Create(hangman);
    aLabel.Name := 'letter_'+IntToStr(X);
    aLabel.Parent := hangman;
    aLabel.Visible := True;
    if word[X] = '-' then
    begin
      aLabel.Caption := '-';//IntToStr(X);
      inc(Q);
    end
    else
    begin
      aLabel.Caption := '_';//IntToStr(X);
    end;
    alabel.Font.Size := 11;
    alabel.Width := 16;
    aLabel.Height := 30;
    aLabel.Top := 10;
    aLabel.Left := 16*X+((hangman.clientwidth div 2)-(word.Length*11));
    alabel.Transparent := false;
    aLabel.Alignment := taRightJustify;
  end;

end;


function getWords() : integer;
var
  wordNumber : integer;
  I : byte;
  //defs : array[0..4] of string;

  cnt : byte;
  randomIndex : byte;
  defs : tStringList;

//  wordLength : byte;
begin
  Q := 0;
  J := 0;
  defs := tStringList.Create;
  //get word, definition, and parts of speach
  wordNumber := random(wordList.Count-1);
  word := wordList[wordNumber];
  definition := definitionList[wordNumber];
  partsOfSpeach := partsOfSpeachList[wordNumber];
//  wordLength := word.Length;

  hangman.Caption := 'The word is '+IntToStr(word.Length)+ ' letters long '+
  'and is a'+partsOfSpeach;

//  ShowMessage(word);

  result := wordNumber;
  correctDefinition := definition;
//  showMessage(correctDefinition+' '+definition);


  defs.Add(definition); //correct definition

  for I := 0 to 3 do
  begin
    defs.Add(definitionList[random(wordList.Count-1)]);
  end;

  for cnt := 0 to -1 + defs.count do
  begin
    randomIndex := random(-cnt + defs.Count);
    defs.Exchange(cnt, cnt + randomIndex);
  end;

  for cnt := 0 to defs.Count -1 do
  begin
    hangman.RadioGroup1.Items.Add(defs[cnt]);
  end;


//  showMessage(word);



//  hangman.RadioGroup1.Items.Count

end;

procedure Thangman.Button1Click(Sender: TObject);
begin
  pressed := 0;
  deleteDashes();
  getWords();
  createDashes();
end;

procedure Thangman.FormCreate(Sender: TObject);
var
  firstSplit : integer;
  secondSplit : integer;
  I : integer;
begin
  KeyPreview := True;


  wordANDdefinition := tStringList.Create;

  definitionList := tStringList.Create;
  wordList := tStringList.create;
  partsOfSpeachList := tStringList.Create;

  wordANDdefinition.LoadFromFile(getcurrentdir+'\newwordlist.txt');

  for I := 0 to wordANDdefinition.Count-1 do
  begin
    firstSplit := pos('|', wordANDdefinition[I]);
    secondSplit := pos('^', wordANDdefinition[I]);
    word := wordANDdefinition[I];
    definition := wordANDdefinition[I];
    partsOfSpeach := wordANDdefinition[I];
    delete(word,firstSplit,length(word)-firstSplit+1);
    definition := copy(definition,secondSplit+1,definition.Length);
    partsOfSpeach := copy(partsOfSpeach,firstSplit+1,(secondSplit-1)-firstSplit);
    wordList.Add(word);
    definitionList.Add(definition);
    partsOfSpeachList.Add(partsOfSpeach);
  end;

  word := wordList[getWords()];
  createDashes();
  wordANDdefinition.Clear();

end;

procedure Thangman.FormKeyPress(Sender: TObject; var Key: Char);
var
  I : byte;

begin


  if CharinSet(Key,['A'..'Z', 'a'..'z']) then
  begin
    for I := 1 to length(word) do
    begin
      if word[I] = lowerCase(Key) then
      begin
        inc(Q);
        TLabel(Components[I+2]).Caption := UpCase(Key);
      end
    end;
//    exclude(key);
  end
  else
  begin
    showMessage('Please only use letters A-Z');
  end;

  if Q = length(word) then
  begin
    KeyPreview := False;
    RadioGroup1.Visible := true;
  end;

  Panel1.Caption := UpCase(Key)+' '+Panel1.Caption;
//  showmessage(IntToStr(J)+' '+IntToStr(length(lettersGuessed)));

end;



procedure Thangman.RadioGroup1Click(Sender: TObject);
begin
  if radiogroup1.Items[radiogroup1.ItemIndex] = correctDefinition then
  begin
    ShowMessage('Correct');
    KeyPreview := true;
    RadioGroup1.Visible := false;
    button1.Click;
    Panel1.Caption := ' ';
  end else
  begin
    ShowMessage('Try again');
  end;

end;

end.

Good that you have something working! That puts you ahead of the game already.

Here are some comments on your code and your question.

  1. Data encapsulation is an important part of OOP.
    You have many variables created in the interface var section. Only the hangman variable (the form) needs to be there. The rest all relate to a single instance of the form so can be moved to be part of the form.

  2. You leak memory when you create objects.
    When you create an object on-the-fly like this:

    wordANDdefinition := tStringList.Create;

You allocate memory for that object. (This hapends behind the scenes). You are responsible for releasing that memory when you no longer need it, so somewhere you need a corresponding line like this:

wordANDdefinition.Free;

An exception to this is when you create a component and specify an Owner (not nil). In this case the Owner will free the component you created when it (the owner) is freed. You can still free your component if you decide earlier that you don't need it any more but it is OK to leave that job for the owner.

I suggest you add a line in your project source (.dpr file):

begin
  ReportMemoryLeaksOnShutdown := TRUE; <<< Add line.
  Application.Initialize;
  Application.MainFormOnTaskbar := True;

When your program closes you will then be informed of any memory leaks.

  1. You are on the right lines with your proposed word object.
    I suggest you define properties for all the data in the word object. (Don't simply put variables in the public section).

You could use a constructor with a string parameter. When you call the constructor, pass in a line from the file. The object can then parse the string to extract word value, part of speech and definion.

BTW Usually you should not use delphi types as variable names or property names. It can get confusing when you come to maintain the code if you see something like MyGame.Words[i].Word and then realise that the variable is not a word but a string. So the word property should be called something like Value or WordString, not Word.

The code to parse the line from the file could be in a private method.

You could also consider adding methods to generate and kill the dashes.

You could consider defining a THangChr class and have the word class hold its data in an array of THangChr objects. The THangChr class could then have properties like GuessedAlready (boolean) and DisplaySring (the letter or a dash).

  1. A TWordPool class might be a good idea.
    This would encapsulate access to the file, selecting a random word etc. It could also hold the word objects.
    You could use a list of THangWord objects, or a collection, whatever you prefer.

  2. The way you are using labels as dashes is suspect.
    Try to find a way to have all dashes displayed by a single component. If you use a label you could set its caption to _ _ _ _ _ _ _ for example, rather than having 7 labels.
    You might also consider writing a custom component to display the dashes. That could be cool.

  3. Your code has some very fragile elements, such as:

    For I := 1 to length(word) do
    begin
    if word[I] = lowerCase(Key) then
    begin
    inc(QByte);
    TLabel(Components[I+2]).Caption := UpCase(Key);
    end
    end;

Components[I+2] sets my teeth on edge. Relying on the relative position of components is very flaky code. It is highly likely that unrelated changes to the app will break this code. Try to re-work it.

  1. Consider defining non-OOP types where they are more suitable than classes.
    For example, define a set type to hold letters.
    One instance of this type could hold letters guessed so far, correct or not.
    Another could hold all acceptable letters ('A' to 'Z' in English).
    Then when the user presses a key, check if it is in the set of acceptable letters and not in the set of previous guesses. If so, check the work, if not throw your toys out of the pram and go no further.

OK, That's enough to be getting on with.

I don't know what went wrong with the formatting of my reply above

It's probably caused by code inside a bulleted list, IIRC a known issue.

Thank you for your reply. You have given me a lot to think about..:) Thanks, I'll let you know where I go from here.

This article has been dead for over six months. Start a new discussion instead.