(C++) Thinking Wt 2: creating a TicTacToe widget
January 10, 2018 · View on GitHub
(C++)
Thinking Wt 2: creating a TicTacToe widget
This article shows the way to create a TicTacToe widget when using the Wt library.
Downloads
- View a screenshot of application developed in step 1 of this article (png)
- View a screenshot of application developed in step 2 of this article (png)
- View a screenshot of application developed in step 3 of this article (png)
- Download the Qt Creator source code of 'CppThinkingWt2_1' (zip)
- Download the Qt Creator source code of 'CppThinkingWt2_2' (zip)
- Download the Qt Creator source code of 'CppThinkingWt2_3' (zip)
Overview
This article follows the same architecture as Thinking Wt 1: general. First the bare-bone architecture is implemented, so the programmer can test the application as early as possible. Next follows the dialog and widget implementation, ending with a conclusion.
Architecture
The architecture, from biggest to smallest, consists of:
- main creates a single class called WtApplication
- WtApplication (a derived class of Wt::WApplication) creates a single dialog, called WtTicTacToeDialog
- WtTicTacToeDialog (a derived class of Wt::WContainerWidget) consists of multiple widgets, among others the WtTicTacToeWidget
- WtTicTacToeWidget (a derived class of Wt::WPaintedWidget) is the widget to display the TicTacToe board. The logic of the TicTacToe game is embodied in a TicTacToe class, which is managed by WtTicTacToeWidget
Step 1: implementing the bare-bone architecture
The purpose of first implementing the bare-bone architecture is to get the program running, so it can be tested. The same architecture as Thinking Wt 1 is used:
#include <boost/signals2.hpp> #include <Wt/WApplication> #include <Wt/WContainerWidget> #include <Wt/WEnvironment> #include <Wt/WPaintDevice> #include <Wt/WPaintedWidget> #include <Wt/WPainter> #include <Wt/WPushButton> #include "tictactoe.h" //--------------------------------------------------------------------------- struct WtTicTacToeWidget : public Wt::WPaintedWidget { WtTicTacToeWidget() { this->resize(32,32); } protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); painter.drawImage(0,0,Wt::WPainter::Image("R.png",32,32)); } private: TicTacToe m_tictactoe; }; //--------------------------------------------------------------------------- struct WtTicTacToeDialog : public Wt::WContainerWidget { WtTicTacToeDialog() : m_tictactoe(new WtTicTacToeWidget) { this->addWidget(m_tictactoe); } private: WtTicTacToeWidget * const m_tictactoe; }; //--------------------------------------------------------------------------- struct WtApplication : public Wt::WApplication { WtApplication(const Wt::WEnvironment& env) : Wt::WApplication(env), m_dialog(new WtTicTacToeDialog) { this->setTitle("Thinking Wt 2: creating a TicTacToe widget"); root()->addWidget(m_dialog); } private: WtTicTacToeDialog * const m_dialog; }; //--------------------------------------------------------------------------- Wt::WApplication *createApplication( const Wt::WEnvironment& env) { return new WtApplication(env); } //--------------------------------------------------------------------------- int main(int argc, char **argv) { return WRun(argc, argv, &createApplication); }
The most notable in this setup is that WtTicTacToeWidget is a derived class of Wt::WPaintedWidget. This is because then there is most control in drawing the TicTacToe board. For now, the widget shows an image called 'R.png'.
Running the Wt application
Add the following line to your Qt project file (to prevent link errors like undefined reference to 'Wt::WRun(int, char**, Wt::WApplication* (*)(Wt::WEnvironment const&))'):
LIBS += -lwt -lwthttp
Additionally, add the following line to your Qt project file, as Wt uses the Boost.Signals library, that needs to be linked to as well:
LIBS += -lboost_signals
Add the following arguments to the Run Settings (to prevent the misc error stat: No such file or directory. Document root ("") not valid.
--docroot . --http-address 0.0.0.0 --http-port 8080
Start the program and your favorite webbrowser. Take the webbrowser to the following address:
http://127.0.0.1:8080/
Step 2: implementing the WtTicTacToeDialog
In this simple example, the WtTicTacToeDialog shows both a WtTicTacToeWidget and a restart button, aligned vertically. The restart button also shows the state of the game (that is: player 1 has won, player 2 has won, draw, game is still unfinished):
#include <boost/signals2.hpp> #include <Wt/WApplication> #include <Wt/WBreak> #include <Wt/WContainerWidget> #include <Wt/WEnvironment> #include <Wt/WEvent> #include <Wt/WPaintDevice> #include <Wt/WPaintedWidget> #include <Wt/WPainter> #include <Wt/WPushButton> #include "tictactoe.h" //--------------------------------------------------------------------------- struct WtTicTacToeWidget : public Wt::WPaintedWidget { WtTicTacToeWidget() { this->resize(32,32); } boost::signals2::signal<void ()> m_signal_state_changed; int GetState() const { return 0; } void Restart() {} protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); painter.drawImage(0,0,Wt::WPainter::Image("R.png",32,32)); } private: TicTacToe m_tictactoe; }; //--------------------------------------------------------------------------- struct WtTicTacToeDialog : public Wt::WContainerWidget { WtTicTacToeDialog() : m_button(new Wt::WPushButton), m_tictactoe(new WtTicTacToeWidget) { this->addWidget(m_tictactoe); this->addWidget(new Wt::WBreak(this)); this->addWidget(m_button); m_button->setText("Restart"); m_tictactoe->m_signal_state_changed.connect( boost::bind( &WtTicTacToeDialog::OnStateChanged, this)); m_button->clicked().connect( this,&WtTicTacToeDialog::OnRestart); } private: Wt::WPushButton * const m_button; WtTicTacToeWidget * const m_tictactoe; void OnRestart() { m_tictactoe->Restart(); } void OnStateChanged() { switch (m_tictactoe->GetState()) { case TicTacToe::player1: m_button->setText("Player 1 has won. Click to restart"); break; case TicTacToe::player2: m_button->setText("Player 2 has won. Click to restart"); break; case TicTacToe::draw: m_button->setText("Draw. Click to restart"); break; case TicTacToe::no_winner: m_button->setText("Restart"); break; default: assert(!"Should not get here"); break; } } };
Note that the widget has only dummy member functions yet, but that the dialog is fully functional. Because I prefer using the same signal/slot system in all my programs, I use the Boost signals instead of the Wt signals. To get the widgets align vertically, I put a Wt::WBreak between the two relevant widgets. The unnamed Wt::WBreak will be deleted by the dialog.
Step 3: implementing the WtTicTacToeWidget
The widget handles the interface between the TicTacToe class and the user's mouse clicks.
#include <boost/signals2.hpp> #include <Wt/WApplication> #include <Wt/WBreak> #include <Wt/WBrush> #include <Wt/WContainerWidget> #include <Wt/WEnvironment> #include <Wt/WEvent> #include <Wt/WPaintDevice> #include <Wt/WPaintedWidget> #include <Wt/WPainter> #include <Wt/WPen> #include <Wt/WPushButton> #include "tictactoe.h" //--------------------------------------------------------------------------- struct WtTicTacToeWidget : public Wt::WPaintedWidget { WtTicTacToeWidget() { //Without resize, there is nothing to paint on this->resize(GetWidth(),GetHeight()); this->clicked().connect(this,&WtTicTacToeWidget::OnClicked); this->update(); } boost::signals2::signal<void ()> m_signal_has_winner; boost::signals2::signal<void ()> m_signal_state_changed; int GetState() const { return m_tictactoe.GetWinner(); } void Restart() { m_tictactoe = TicTacToe(); this->update(); } protected: void paintEvent(Wt::WPaintDevice *paintDevice) { Wt::WPainter painter(paintDevice); const int width = GetWidth(); const int height = GetHeight(); //Set black pen Wt::WPen pen = painter.pen(); pen.setCapStyle(Wt::RoundCap); pen.setColor(Wt::WColor(255,255,255)); painter.setPen(pen); painter.setBrush(Wt::WBrush(Wt::WColor(255,255,255))); painter.drawRect(0.0,0.0,GetWidth(),GetHeight()); //Set thick white pen pen.setColor(Wt::WColor(0,0,0)); const int line_width = std::min(width,height) / 15; pen.setWidth(line_width); painter.setPen(pen); //Vertical lines painter.drawLine( ((1*width)/3)+4, 0+(line_width/2), ((1*width)/3)-4,height-(line_width/2)); painter.drawLine( ((2*width)/3)-4, 0+(line_width/2), ((2*width)/3)+8,height-(line_width/2)); //Horizontal lines painter.drawLine( 0+(line_width/2),((1*height)/3)+4, width-(line_width/2),((1*height)/3)-4); painter.drawLine( 0+(line_width/2),((2*height)/3)-4, width-(line_width/2),((2*height)/3)+8); for (int row=0; row!=3; ++row) { const int x1 = ((row + 0) * (width / 3)) + (line_width/1) + 4; const int x2 = ((row + 1) * (width / 3)) - (line_width/1) - 4; for (int col=0; col!=3; ++col) { const int y1 = ((col + 0) * (height / 3)) + (line_width/1) + 4; const int y2 = ((col + 1) * (height / 3)) - (line_width/1) - 4; const int state = m_tictactoe.GetSquare(row,col); if (state == TicTacToe::player1) { //player1 = cross painter.drawLine(x1,y1,x2,y2); painter.drawLine(x1,y2,x2,y1); } else if (state == TicTacToe::player2) { //player1 = circle painter.drawEllipse(x1,y1,x2-x1,y2-y1); } } } } private: TicTacToe m_tictactoe; int GetWidth() const { return 300.0; } int GetHeight() const { return 300.0; } void OnClicked(const Wt::WMouseEvent& e) { if (m_tictactoe.GetWinner() != TicTacToe::no_winner) return; const int x = 3 * e.widget().x / this->GetWidth(); if (x < 0 || x > 2) return; const int y = 3 * e.widget().y / this->GetHeight(); if (y < 0 || y > 2) return; if (m_tictactoe.CanDoMove(x,y)) { m_tictactoe.DoMove(x,y); //emit that the state has changed this->m_signal_state_changed(); } if (m_tictactoe.GetWinner() != TicTacToe::no_winner) { //emit that there is a winner this->m_signal_has_winner(); } this->update(); } };
Conclusion
This article described the gradual development of a custom dialog and widget. Using the architecture described in Thinking Wt 1: general, it is possible to have multiple steps-in-between to check if the program still works.
The tool TestTicTacToe contains the polished and slightly extended version of the code in this article.
My next article, Thinking Wt 3: TicTacToe game describes how I implement the WtTicTacToeWidget in a full game.
- View a screenshot of application developed in step 1 of this article (png)
- View a screenshot of application developed in step 2 of this article (png)
- View a screenshot of application developed in step 3 of this article (png)
- Download the Qt Creator source code of 'CppThinkingWt2_1' (zip)
- Download the Qt Creator source code of 'CppThinkingWt2_2' (zip)
- Download the Qt Creator source code of 'CppThinkingWt2_3' (zip)
External links
References
- Wt homepage
- Victor Volkman. Wt: C++ Web Toolkit Library Lets You Write Scripting-Independent Web Apps. www.codeguru.com
- Wt homepage, source code of the 'Hello world' example
Acknowledgements
Thanks Tor Arne Fallingen for notifying me that I omitted linking to Boost.Signals.