Why my code doesn't show my TButton's array? - c++

I'm making a simple hangman game in c++ builder community edition and my game consists of buttons that represents letters and if the letter doesn't appear inside of the word you lose a life, and so on, and so on...
But I'd though that is a little repetitive my code if I make a TButton for every letter in the abcedary. So I decide to make an array of TButton my surprise was when I code everything and any of them appear in my form :c.
If someone can help me a little I'll be so happy haha.
Tgame class...
class Tgame : public TForm
{
__published: // IDE-managed Components
TText *word;
private: // User declarations
TButton* chars[23];
public: // User declarations
__fastcall Tgame(TComponent* Owner);
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
};
And the constructor implementation...
for(int i = 0; i < 23; ++i) {
this->chars[i] = new TButton(this);
this->chars[i]->Height = 33;
this->chars[i]->Width = 49;
this->chars[i]->Position->X = startX;
this->chars[i]->Position->Y = startY;
startX += difX;
startY += difY;
this->chars[i]->Opacity = 1;
this->chars[i]->Visible = true;
this->chars[i]->Text = "A";
}

You construct the TButton setting its owner (the component responsible for deleting it).
this->chars[i] = new TButton(this);
But you don't set its Parent, which is the component in which the TButton will appear visually. So, add this line:
this->chars[i]->Parent = this;
Note: The default values for Opacity and Visible are 1 and true so you don't need to set those explicitly.

Related

What to pass as a Sender in a button OnClick method?

I have a function that creates a button dynamically
void createBtn(News obj,TForm *Form1){
TButton *spam = new TButton(Form1);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = "Spam";
}
I need to assign an OnClick event to it, so I added the following line to the function above:
spam->OnClick = spamClick;
The code for spamClick is:
void __fastcall TForm1::spamClick(TObject *Sender, News obj)
{
allUsers[userIndex].spamNews(obj);
ShowMessage("Done!");
}
The problem is, I need to pass the obj in the line, but the function requires 2 arguments, which are the Sender and the obj.
spam->OnClick = spamClick(obj); // error here
But I do not know what to pass. I've tried Form1, spam, and newsCard. But nothing works.
What do I pass as a Sender? Or, is there a way to assign an OnClick event to the button inside createBtn()?
Edit:
class News has the following definition
class News
{
public:
News(string, string, string, Dates);
string title;
string description;
Dates date;
int rate;
string category;
vector<Comment> comments;
int spamCount;
static int newsCount;
int newsID;
int numOfRatedUsers;
};
and spamNews is a function in the user class that pushes the obj.newsID into a vector in the user then increases the spamCount.
void user::spamNews(News& obj) {
//check that the news in not already spammed
if(!findNews(spammedNews,obj)){
spammedNews.push_back(obj.newsID);
obj.spamCount++;
}
}
Your second approach doesn't work, because you are trying to call spamClick() first and then assign its return value to the OnClick event.
Your first approach is the correct way, however you can't add parameters to the OnClick event handler.
TButton has Tag... properties for holding user-defined data. However, since the News object is not being passed around by pointer, the Tag... properties are not very helpful in this case (unless the News object is held in an array/list whose index can then be stored in the Tag).
Otherwise, I would suggest deriving a new class from TButton to hold the News object, eg:
class TMyButton : public TButton
{
public:
News NewsObj;
__fastcall TMyButton(TComponent *Owner, const News &obj)
: TButton(Owner), NewsObj(obj) {}
};
void TForm1::createBtn(const News &obj)
{
TMyButton *spam = new TMyButton(this, obj);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(btn->NewsObj);
ShowMessage(_D("Done!"));
}
UPDATE: Since your News objects are being stored in a vector that you are looping through, then a simpler solution would be to pass the News object to createBtn() by reference and then store a pointer to that object in the TButton::Tag property, eg:
void TForm1::createBtn(News &obj)
{
TButton *spam = new TButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->Tag = reinterpret_cast<NativeInt>(&obj);
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
TButton *btn = static_cast<TButton*>(Sender);
News *obj = reinterpret_cast<News*>(btn->Tag);
allUsers[userIndex].spamNews(*obj);
ShowMessage(_D("Done!"));
}
Or, using the TMyButton descendant:
class TMyButton : public TButton
{
public:
News *NewsObj;
__fastcall TMyButton(TComponent *Owner)
: TButton(Owner) {}
};
void TForm1::createBtn(News &obj)
{
TMyButton *spam = new TMyButton(this);
spam->Parent = newsCard;
spam->Position->X = 280;
spam->Position->Y = 256;
spam->Text = _D("Spam");
spam->NewsObj = &obj;
spam->OnClick = &spamClick;
}
void __fastcall TForm1::spamClick(TObject *Sender)
{
MyButton *btn = static_cast<TMyButton*>(Sender);
allUsers[userIndex].spamNews(*(btn->NewsObj));
ShowMessage(_D("Done!"));
}

Faster way to create TCombobox at Runtime

I want to fill a form at runtime with a lot of comboboxes with identical lists. They also get the same event handler, which is acting depending on the Sender objects's Name. However, this takes a pretty long time and I was guessing I do something wrong.
I'm using XE2 Rad Studio C++ Builder and the VCL GUI.
Edit: Those boxes contain a different kinds of content and are distributed over a few tabPages within the form. however, it's necessary to display what it selected at at least 80 of them at a glance. Would it maybe be better to replace them with TLabels and create a TCombobox when clicking on the TLabel to select a different element?
The Code looks similar to this:
void __fastcall TForm::TForm(){
int i=0;
TStringList* targetlist = new TStringList();
targetlist->Add("Normal");
targetlist->Add("Inverted");
Vcl::Stdctrls::TComboBox **com = new Vcl::Stdctrls::TComboBox[512];
for(i=0;i<512;++i){
com[i]=new Vcl::Stdctrls::TComboBox(this);
com[i]->Parent=this;
com[i]->Name.printf(L"Combo_%d", i);
com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
com[i]->Items = targetlist;
com[i]->ItemIndex = 0;
com[i]->Style = csDropDownList;
com[i]->OnChange = MyComboTriggerChange;
}
}
One iteration seems to take around 20ms on my machine (testedt with std::clock), which make this part ~10s long. The pointers are deleted at the form's destruction. I just put their declarations here for simplifications.
Is there a better way to create multiple comboboxes? Maybe clone them?
You seriously need to redesign your UI. Using 512 TComboBox controls on one screen with the same list of values makes no logical sense, and is a waste of time and resources. There are better ways to display 512 strings on a screen, such as a TListView in report mode, or a TListBox (both of them support a virtual mode so they can share common data without wasting memory). Or use a TValueListEditor or TStringGrid with an esPickList inline editor. Or, if you are really adventurous, write a custom control from scratch so you use 1 efficient control instead of 512 separate controls. Anything is better than 512 TComboBox controls.
That being said, TComboBox does not support a virtual mode, like TListBox and TListView do, but there are a couple of optimizations you can still make to speed up your TComboBoxes a little:
don't make 512 copies of the same TStringList content. Anything you add to the TComboBox::Items is stored inside the TComboBox's memory. You should strive to reuse your single TStringList and let everything delegate to it as needed. In this case, you can set the TComboBox::Style property to csOwnerDrawFixed and use the TComboBox::OnDrawItem event to draw the TStringList strings on-demand. You still need to add strings to each TComboBox, but they can be empty strings, at least.
subclass TComboBox to override its virtual CreateParams() method and it remove the CBS_HASSTRINGS window style, then the TComboBox does not actually need to store empty strings in its memory.
Try something like this:
class TMyComboBox : public Vcl::Stdctrls::TComboBox
{
typedef Vcl::Stdctrls::TComboBox inherited;
private:
TStrings *fSharedItems;
void __fastcall SetSharedItems(TStrings *Values)
{
if (fSharedItems != Values)
{
fSharedItems = Values;
Items->BeginUpdate();
try
{
Items->Clear();
if (fSharedItems)
{
for (int i = 0; i < fSharedItems->Count; ++i)
Items->Add(L"");
}
}
__finally
{
Items->EndUpdate();
}
}
}
protected:
virtual void __fastcall CreateParams(TCreateParams &Params)
{
inherited::CreateParams(Params);
Params.Style &= ~CBS_HASSTRINGS;
}
virtual __fastcall DrawItem(int Index, TRect Rect, TOwnerDrawState State)
{
// draw the items however you want...
if (fSharedItems)
Canvas->TextRect(Rect.Left, Rect.Top, fSharedItems->Strings[Index]);
}
public:
__fastcall TMyComboBox(TComponent *Owner)
: Vcl::Stdctrls::TComboBox(Owner)
{
Style = csOwnerDrawFixed;
}
__property TStrings* SharedItems = {read=fSharedItems, write=SetSharedItems};
};
class TMyForm : public TForm
{
...
private:
TStringList* targetlist;
TMyComboBox **com;
void __fastcall MyComboTriggerChange(TObject *Sender);
...
public:
__fastcall TMyForm(TComponent *Owner);
__fastcall ~TMyForm();
...
};
__fastcall TMyForm::TMyForm(TComponent *Owner)
: TForm(Owner)
{
targetlist = new TStringList;
targetlist->Add("Normal");
targetlist->Add("Inverted");
com = new TMyComboBox*[512];
for(int i=0;i<512;++i)
{
com[i] = new TMyComboBox(this);
com[i]->Parent = this;
com[i]->Name = String().sprintf(L"Combo_%d", i);
com[i]->SetBounds(10, 198 + 20 * i, 130, 200);
com[i]->SharedItems = targetlist;
com[i]->ItemIndex = 0;
com[i]->OnChange = &MyComboTriggerChange;
}
}
__fastcall TMyForm::~TMyForm()
{
delete targetlist;
delete[] com;
}
void __fastcall TMyForm::MyComboTriggerChange(TObject *Sender)
{
TMyComboBox *cb = static_cast<TMyComboBox*>(Sender);
// use targetlist->Strings[cb->ItemIndex] as needed...
}

Spawning waves of enemies C++

I'm creating a simple game with qt 5.0.1. It's something like Warblade.
I have problem with creating waves of enemies.
int k;
int pos = 100;
for (k = 0; k < 5; k++)
{
pos = 100;
for (int i = 0; i < 9; i++)
{
player->spawn_in_pos(pos);
pos += 100;
}
//QThread::sleep(2);
}
When i use sleep() function, my game just can't run. It's waiting for loop finish and then it shows.
I'm also dealing with second option:
QTimer * timer = new QTimer();
QObject::connect( timer, SIGNAL(timeout()), player, SLOT(spawn_in_pos(pos)) );
timer->start(450);
But it looks like SLOT can't get the position.
Edit:
I just did what #ddriver said, and that helped me a lot.
Now I'm getting some 'laggy' style enemies movement.
Edit2:
I'm moving my enemies down like this:
setPos(x(),y()+1);
with that timer:
// connect
QTimer * timer = new QTimer(this);
connect(timer,SIGNAL(timeout()),this,SLOT(move()));
// start the timer
timer->start(10);
It looks like very smooth movement but probably +1 pixel down and a 10 timer is to less:((
I'm not sure what you are trying to achieve, but in your second option, you cannot get the position, because the timeout doesn't send it.
The signal is timeout(void) and your slot expects an parameter. I guess you lack some basic understanding of the signal/slot mechanism.
The QT Documentation is pretty neat:
http://doc.qt.io/qt-5/signalsandslots.html
And if you just want to create a game out of nothing, here you can find a little tutorial, how to write games in QT:
https://www.youtube.com/watch?v=8ntEQpg7gck
Calling sleep is going to stop the thread from processing anything, which is not what you want to do.
Using C++ 11, you can use the QTimer with a lambda function like this: -
int pos = 100;
int nextWaveTime = 2000; // 2 seconds per wave
for (k = 0; k < 5; k++) // 5 waves of enemies
{
for (int i = 0; i < 9; i++) // 9 enemies per wave
{
QTimer * timer = new QTimer();
timer->setSingleShot(true);
pos = pos + (100*i); // set the pos, which is captured by value, in the lambda function
QObject::connect( timer, QTimer::timeout, [=](){
player->spawn_in_pos(pos);
timer->deleteLater(); // must cleanup the timer
});
timer->start(450 + (k*nextWaveTime));
}
}
In order to pass parameters with signals and slots in Qt, the signal parameters must match the parameters of the slot (or function since Qt 5).
One way to solve the issue is to use a lambda as in TheDarkKnight's answer.
What I would suggest is to use encapsulation - you could create a Spawner object, dedicated to spawning enemies and keep the position internal to it. This way the spawner will manage the position, and you can have something like Spawner::createWave() slot with no parameters, since the position is internal. Then setup the timer and connect it to createWave() and you are set.
Also it is a very bad idea to hardcode stuff like that, you really need more flexibility, the option to change enemy and wave count, the wave time as well as the screen width, so that your game can change those things as it gets harder.
class Spawner : public QObject {
Q_OBJECT
public:
Spawner(int wCount = 5, int eCount = 9, int time = 2000, int sWidth = 1000)
: waveCount(wCount), enemyCount(eCount), currentWave(0), screenWidth(sWidth) {
timer.setInterval(time);
connect(&timer, SIGNAL(timeout()), this, SLOT(createWave()));
}
void set(int wCount, int eCount, int time) {
timer.setInterval(time);
waveCount = wCount;
enemyCount = eCount;
}
void changeWidth(int w) { screenWidth = w; }
public slots:
void start() { timer.start(); }
void stop() {
timer.stop();
currentWave = 0;
}
private slots:
void createWave() {
int pos = screenWidth / (enemyCount + 1);
int step = pos;
for (int i = 0; i < enemyCount; ++i) {
Game::spawnEnemyAt(pos);
pos += step;
}
if (++currentWave >= waveCount) stop();
}
private:
QTimer timer;
int waveCount, enemyCount, currentWave, screenWidth;
};
Create a Spawner object and connect the game new level to start() - it will span the given number waves of enemies evenly across the game screen, when you finish the waves off, you adjust the spawner settings and start a new level.
That encapsulation will come in handy later on as your game becomes less of a test and more like a real game - with increasing difficulty, changing spawning and attack patterns and so on. So it is a good idea to implement it right from the start and build upon a good and flexible design rather than going back and changing stuff around, which may break other code. You really don't want to start without a good design and make design changes later. Thus the need to encapsulate functionality and responsibility and just connect the pieces rather than building on a pile of spaghetti code. In this line of thought, I noticed you are using player->spawn_in_pos(pos); - which is an example of bad design, as spawning should be a responsibility of the Game class, not the Player class. A good design is not only flexible, but also clean. The Spawner object is only responsible for spawning waves of enemies, and its visible interface is limited to start(), stop() and set().
Edit:
class Game : public QObject {
Q_OBJECT
public:
Game() {
if (!scene) scene = new QGraphicsScene(this);
connect(this, SIGNAL(newLevel()), &spawner, SLOT(start()));
}
static void spawnEnemyAt(int x = 0) {
scene->addItem(new Enemy(x, 0));
qDebug() << "enemy created";
}
public slots:
void newGame() {
// initialize game
emit newLevel(); // begin spawning
}
void onLevelEnd() {
// spawner.set(new level settings);
emit newLevel();
}
void onGameEnded() {
// ...
}
signals:
void newLevel();
private:
Spawner spawner;
static QGraphicsScene * scene;
};
// in game.cpp
QGraphicsScene * Game::scene = nullptr;
If you don't want to use static members, you can make spawnEnemyAt() and scene instance members, but then you will have to pass the Game instance to the Spawner in the constructor so that you have a reference to the game the spawner operates on and use game->spawnEnemyAt() instead. This way you can create multiple games with their own dedicated scenes. Or parent the spawner to the game and cast the spawner's parent() to a Game * to access the game instance which is a little hacky, but saves on the extra member by reusing the parent.

Adding sprite to a node

I've got a SpritePlayer class, which holds my sprite.
SpritePlayer.h:
class SpritePlayer : public cocos2d::Node
{
public:
SpritePlayer();
CREATE_FUNC(SpritePlayer);
void InitSpritePlayer(std::string pathToSptire);
cocos2d::Sprite *GetSprite();
(...)
private:
cocos2d::Sprite *_sprite;
}
SpritePlayer.cpp:
void SpritePlayer::InitSpritePlayer(std::string pathToSprite)
{
_sprite = cocos2d::Sprite::create(pathToSprite);
}
cocos2d::Sprite *SpritePlayer::GetSprite()
{
return _sprite;
}
(...)
At MainScene.cpp I've got:
for (int i = 0; i < 4; i++)
{
playerSpritesList[i] = &SpritePlayer();
playerSpritesList[i]->InitSpritePlayer("ch2.png");
this->addChild(playerSpritesList[i]->GetSprite(), 0);
//SpritePlayersNode->addChild(playerSpritesList[i]->GetSprite())
}
And now the question - how could I add this sprite to a node?
Both bottom lines are causing errors, because I have to pass a Node into addChild() function.
The way you are going about it is introducing a level of abstraction that you do not need to have. The character itself can be a sprite, the way you have it your SpriteCharacter is not actually a sprite, it's a manager for a character sprite. I usually use the following pattern.
Character.h
class Character : public cocos2d::Sprite
{
public:
Character* createCharacterSprite(Vec2 position, std::string fileName);
private:
Character();
}
Character.cpp
Character* Character::createCharacterSprite(Vec2 position, std::string fileName)
{
auto character = new Character();
if(character && character->initWithFile(fileName))
{
character->autorelease();
return character;
}
}
MainScene.cpp
for (int i = 0; i < 4; i++)
{
auto character = Character::createCharacterSprite(characterPosition, "filename.png");
this->addChild(character);
}
This way you can manipulate from within CharacterSprite using 'this' instead of a pointer to your actual character sprite. Positioning and animations will also become a lot easier since you won't have another node with a possible different anchor point in between your character and your MainScene layer.
Sprite is a subclass of Node so there's not a problem with using addChild.
This line is suspicious:
playerSpritesList[i] = &SpritePlayer();
I'd remove SpritePlayer() constructor from your code, because CREATE_FUNC(SpritePlayer) creates default one, which manages memory. And then you can call playerSpritesList[i] = SpritePlayer::create();
Also you can write USING_NS_CC; in SpritePlayer (beware of Point struct - you have to write cocos2d::Point, because of namespace conflict on iOS/Mac).
Also for convention function names should start with lower case :)

OOP Game Menu System Development Conceptualising

I'm trying to write an OO menu system for a game, loosely based on the idea of a Model,View,Controller. In my app so far I've named the views "renderers" and the models are without a suffix. I created a generic menu class which stores the items of a menu, which are menu_item objects, and there is also a menu renderer class which creates renderers for each item and renders them. The problem is I'm not sure where to store the data and logic to do with where on the screen each item should be positioned, and how to check if it is being hovered over, etc. My original idea was to store and set a selected property on each menu item, which could be rendered differently by different views, but even then how to I deal with positioning the graphical elements that make up the button?
Code excerpts so far follow: (more code at https://gist.github.com/3422226)
/**
* Abstract menu model
*
* Menus have many items and have properties such as a title
*/
class menu {
protected:
std::string _title;
std::vector<menu_item*> _items;
public:
std::string get_title();
void set_title(std::string);
std::vector<menu_item*> get_items();
};
class menu_controller: public controller {
private:
menu* _menu;
public:
menu_controller(menu*);
virtual void update();
};
class menu_item {
protected:
std::string _title;
public:
menu_item(std::string title);
virtual ~menu_item();
std::string get_title();
};
class menu_renderer: public renderer {
private:
menu* _menu;
bitmap _background_bitmap;
static font _title_font;
std::map<menu_item*, menu_item_renderer*> _item_renderers;
public:
menu_renderer(menu*);
virtual void render();
};
font menu_renderer::_title_font = NULL;
menu_renderer::menu_renderer(menu* menu) {
_menu = menu;
_background_bitmap = ::load_bitmap("blackjack_menu_bg.jpg");
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",48);
}
void menu_renderer::render() {
::draw_bitmap(_background_bitmap, 0, 0);
/* Draw the menu title */
const char* title = _menu->get_title().c_str();
int title_width = ::text_width(_title_font, title);
::draw_text(title, color_white, _title_font, screen_width() - title_width - 20, 20);
/* Render each menu item */
std::vector<menu_item*> items = _menu->get_items();
for (std::vector<menu_item*>::iterator it = items.begin(); it != items.end(); ++it) {
menu_item* item = *it;
if (!_item_renderers.count(item))
_item_renderers[item] = new menu_item_renderer(item, it - items.begin());
_item_renderers[item]->render();
}
}
class menu_item_renderer: public renderer {
private:
unsigned _order;
menu_item* _item;
static font _title_font;
public:
menu_item_renderer(menu_item*, unsigned);
virtual ~menu_item_renderer();
virtual void render();
};
font menu_item_renderer::_title_font = NULL;
menu_item_renderer::menu_item_renderer(menu_item* item, unsigned order) {
_item = item;
_order = order;
if (!_title_font)
_title_font = ::load_font("maven_pro_regular.ttf",24);
}
menu_item_renderer::~menu_item_renderer() {
// TODO Auto-generated destructor stub
}
void menu_item_renderer::render() {
const char* title = _item->get_title().c_str();
int title_width = ::text_width(_title_font, title);
unsigned y = 44 * _order + 20;
::fill_rectangle(color_red, 20, y, title_width + 40, 34);
::draw_text(title, color_white, _title_font, 30, y + 5);
}
Your Model class menu needs a add_view(menuview *v) and update() method.
Then you can delegate the update of your widget to the derived View (menu_renderer or a cli_menu_renderer).
The Controller needs to know the Model (as member), when the Controller runs (or executes a Command) and has to update the Model with a Setter (like m_menu_model->set_selected(item, state)) and the Model calls update() on Setters.
Your Controller menu_controller has a update method, there you could also ask for Input, like if (menuview->toggle_select()) m_menu_model->toggle_selected(); (which all menuviews have to implement) and invoke the setter, but thats a inflexible coupling of View and Controller (you could check MVC with the Command Pattern for a more advanced combination).
For the Position you can set member vars like int m_x, m_y, m_w, m_h.
But these members are specific to a View with GUI, so only the derived View needs them.
Then you could use these values to compare against mouse Positions and use a MouseOver Detection Method like this:
// View menu_item
bool menu_item::over()
{
if (::mouse_x > m_x
&& ::mouse_x < m_x + m_w
&& ::mouse_y > m_y
&& ::mouse_y < m_y + m_h) {
return true;
}
return false;
}
// update on gui menu item
bool menu_item::update()
{
if (over()) {
m_over = true;
}
else {
m_over = false;
}
// onclick for the idea
if ((::mouse_b & 1) && m_over) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
// update the command line interface menu item
bool cli_menu_item::update()
{
if ((::enterKeyPressed & 1) && m_selected) {
// here you could invoke a callback or fire event
m_selected = 1;
} else {
m_selected = 0;
}
return m_selected;
}
void menu_item_renderer::render() {
// update widgets
_item->update();
// ...
}
// Model
void menu::add_view(menuview *v) {
m_view=v;
}
void menu::update() {
if (m_view) m_view->update();
}
bool menu::set_selected(int item, int state) {
m_item[index]=state;
update();
}