Verified Commit 838273ef authored by Romain Lebbadi-Breteau's avatar Romain Lebbadi-Breteau
Browse files

Improve network

Add cancelLastMove() to Board
Create networkMenu in ChessNetwork
Use QDataStream for network transfer
Check if side and turn are correct in ChessNetwork
Put connexion dialog in ChessNetwork
Add invalidMove function and signal
Better network messages with dedicated struct
Add magic number and version to network messages
Check if network message is valid
Add update() and cancelLastMove() in Window
parent 73f84347
......@@ -140,6 +140,17 @@ namespace backend {
return &history_[historyIndex_ - 1];
}
std::optional<Move> Board::cancelLastMove()
{
if (history_.size() == 0) return {};
Move move = history_.back();
history_.pop_back();
move.cancel();
switchTurn();
if (historyIndex_ == history_.size()) historyIndex_--;
return std::move(move);
}
void Board::move(Coordinate from, Coordinate to, Piece* piece)
{
board_.get(to) = piece;
......@@ -258,7 +269,7 @@ namespace backend {
}
/**
* Adds support for operator<< between an ostream and a Board.
* Adds support for operator<< between an ostream and a Board.
*/
std::ostream& operator<<(std::ostream& o, const Board& board)
{
......
......@@ -18,6 +18,7 @@
#include <stdexcept>
#include <memory>
#include <unordered_map>
#include <optional>
#include "Array.hpp"
#include "Side.hpp"
......@@ -135,6 +136,8 @@ namespace backend {
*/
const Move* play(Coordinate from, Coordinate to);
std::optional<Move> cancelLastMove();
/**
* This method moves the piece from the given origin to the
* destination.
......
#include "ChessNetwork.h"
#include <QLineEdit>
#include <QBoxLayout>
#include <QPushButton>
namespace network {
ChessNetwork::ChessNetwork() :
connexionDialog_(new QDialog()),
networkMenu_(new QMenu("Network"))
{
QLineEdit* addressInput = new QLineEdit("localhost");
QLineEdit* portInput = new QLineEdit("2468");
QPushButton* okButton = new QPushButton("Ok");
QHBoxLayout* layout = new QHBoxLayout();
layout->addWidget(addressInput);
layout->addWidget(portInput);
layout->addWidget(okButton);
connexionDialog_->setLayout(layout);
connect(okButton, &QPushButton::clicked, connexionDialog_, &QDialog::accept);
connect(connexionDialog_, &QDialog::accepted, this, [this, addressInput, portInput]() {
connectToHost(addressInput->text(), portInput->text().toInt());
});
auto* listenAction = new QAction("&Listen", networkMenu_);
connect(listenAction, &QAction::triggered, this, &ChessNetwork::startServer);
networkMenu_->addAction(listenAction);
auto* connectAction = new QAction("&Connect...", networkMenu_);
connect(connectAction, &QAction::triggered, connexionDialog_, &QDialog::exec);
networkMenu_->addAction(connectAction);
}
ChessNetwork::~ChessNetwork()
{
if (connexion_) delete connexion_;
if (server_) delete server_;
delete connexionDialog_;
delete networkMenu_;
if (connexion_ != nullptr) delete connexion_;
if (server_ != nullptr) delete server_;
}
QMenu* ChessNetwork::getNetworkMenu()
{
return networkMenu_;
}
void ChessNetwork::startServer()
......@@ -13,58 +52,122 @@ namespace network {
server_ = new QTcpServer;
server_->listen(QHostAddress("0.0.0.0"), 2468);
QObject::connect(server_, &QTcpServer::newConnection, this, &ChessNetwork::receiveConnexion);
connect(server_, &QTcpServer::newConnection, this, &ChessNetwork::receiveConnexion);
}
void ChessNetwork::connect(const QString& hostName, quint16 port)
void ChessNetwork::connectToHost(const QString& hostName, quint16 port)
{
if (connexion_ != nullptr || server_ != nullptr) return;
connexion_ = new QTcpSocket;
connexion_->connectToHost(hostName, port);
QObject::connect(connexion_, &QTcpSocket::readyRead, this, &ChessNetwork::receiveData);
connect(connexion_, &QTcpSocket::readyRead, this, &ChessNetwork::receiveData);
dataBuffer_.setDevice(connexion_);
}
void ChessNetwork::sendNewGame()
void ChessNetwork::sendNewGame(Side side)
{
if (connexion_ == nullptr) return;
qDebug() << "Sending new game";
NetworkMessage message = NetworkMessage(MessageType::NewGame);
connexion_->write("1");
dataBuffer_ << message << static_cast<Side>(!static_cast<bool>(side));
gameStarted_ = true;
side_ = side;
turn_ = Side::White;
emit startGame(side);
}
void ChessNetwork::sendMove(Coordinate from, Coordinate to)
{
if (connexion_ == nullptr) return;
if (connexion_ == nullptr || !gameStarted_) return;
QString string = "2" + QString(from.x) + QString(from.y) + QString(to.x) + QString(to.y);
if (turn_ != side_) {
emit invalidMove();
return;
}
qDebug() << "Sending move";
NetworkMessage message = NetworkMessage(MessageType::Move);
dataBuffer_ << message << from << to;
turn_ = static_cast<Side>(!static_cast<bool>(turn_));
}
void ChessNetwork::sendInvalidMove()
{
if (connexion_ == nullptr || !gameStarted_ || turn_ != side_) return;
qDebug() << "Sending invalid move";
NetworkMessage message = NetworkMessage(MessageType::InvalidMove);
connexion_->write(string.toUtf8());
dataBuffer_ << message;
turn_ = static_cast<Side>(!static_cast<bool>(turn_));
}
void ChessNetwork::receiveConnexion()
{
qDebug() << "Connexion received";
connexion_ = server_->nextPendingConnection();
QObject::connect(connexion_, &QTcpSocket::readyRead, this, &ChessNetwork::receiveData);
dataBuffer_.setDevice(connexion_);
connect(connexion_, &QTcpSocket::readyRead, this, &ChessNetwork::receiveData);
server_->pauseAccepting();
sendNewGame(Side::White);
}
void ChessNetwork::receiveData()
{
QByteArray result = connexion_->read(connexion_->bytesAvailable());
switch (result[0])
dataBuffer_.startTransaction();
NetworkMessage message;
dataBuffer_ >> message;
if (message.isValid())
{
case '1':
newGameReceived_ = true;
if (newGameSent_)
switch (message.messageType_)
{
case MessageType::None:
if (!dataBuffer_.commitTransaction()) return;
qDebug() << "Empty network message received";
break;
case MessageType::NewGame:
Side side;
dataBuffer_ >> side;
if (!dataBuffer_.commitTransaction()) return;
qDebug() << "New game received";
gameStarted_ = true;
emit startGame();
}
break;
side_ = side;
turn_ = Side::White;
emit startGame(side);
return;
case MessageType::Move:
Coordinate from, to;
dataBuffer_ >> from >> to;
if (!dataBuffer_.commitTransaction()) return;
qDebug() << "Move received";
if (side_ == turn_)
{
emit invalidMove();
}
else
{
emit doMove(from, to);
turn_ = static_cast<Side>(!static_cast<bool>(turn_));
}
return;
case '2':
emit doMove({ result[1], result[2] }, { result[3], result[4] });
case MessageType::InvalidMove:
qDebug() << "Invalid move message received";
if (!dataBuffer_.commitTransaction()) return;
emit invalidMove();
turn_ = static_cast<Side>(!static_cast<bool>(turn_));
return;
}
}
dataBuffer_.commitTransaction();
}
}
\ No newline at end of file
#pragma once
#include <stdexcept>
#include <QTcpSocket>
#include <QTcpServer>
#include <QDialog>
#include <QDataStream>
#include <QMenu>
namespace network
{
struct Coordinate
{
char x;
char y;
qint8 x;
qint8 y;
};
enum class Side
{
White,
Black
};
inline QDataStream& operator<<(QDataStream& s, const Coordinate& coordinate)
{
return s << coordinate.x << coordinate.y;
}
inline QDataStream& operator>>(QDataStream& s, Coordinate& coordinate)
{
return s >> coordinate.x >> coordinate.y;
}
enum class MessageType
{
None,
NewGame,
Move,
InvalidMove
};
struct NetworkMessage
{
constexpr static quint32 MAGIC_NUMBER = 0x7c28fe7b;
constexpr static quint16 CURRENT_VERSION = 1;
quint32 magicNumber_;
quint16 version_;
MessageType messageType_;
NetworkMessage(MessageType messageType = MessageType::None) :
magicNumber_(MAGIC_NUMBER),
version_(CURRENT_VERSION),
messageType_(messageType)
{
}
QDataStream& toDataStream(QDataStream& s) const
{
return s << magicNumber_ << version_ << messageType_;
}
QDataStream& fromDataStream(QDataStream& s)
{
return s >> magicNumber_ >> version_ >> messageType_;
}
bool isValid() const
{
return magicNumber_ == MAGIC_NUMBER && version_ == CURRENT_VERSION;
}
};
inline QDataStream& operator<<(QDataStream& s, const NetworkMessage& message)
{
return message.toDataStream(s);
}
inline QDataStream& operator>>(QDataStream& s, NetworkMessage& message)
{
return message.fromDataStream(s);
}
class ChessNetwork : public QObject
{
Q_OBJECT
public:
ChessNetwork();
~ChessNetwork();
QMenu* getNetworkMenu();
void startServer();
void connect(const QString& hostName, quint16 port);
void sendNewGame();
void connectToHost(const QString& hostName, quint16 port);
void sendNewGame(Side side);
void sendMove(Coordinate from, Coordinate to);
void sendInvalidMove();
signals:
void startGame();
void startGame(Side side);
void doMove(Coordinate from, Coordinate to);
void invalidMove();
private slots:
void receiveConnexion();
void receiveData();
private:
QMenu* networkMenu_{ nullptr };
QDialog* connexionDialog_{ nullptr };
QTcpServer* server_{ nullptr };
QTcpSocket* connexion_{ nullptr };
bool newGameSent_{ false };
bool newGameReceived_{ false };
QDataStream dataBuffer_;
bool gameStarted_{ false };
Side side_;
Side turn_;
};
}
......@@ -85,36 +85,12 @@ namespace frontend {
connect(nextAction, &QAction::triggered, this, &Window::redo);
historyMenu->addAction(nextAction);
auto* networkMenu = menuBar()->addMenu("&Network");
auto* listenAction = new QAction("&Listen", this);
connect(listenAction, &QAction::triggered, &chessNetwork_, &network::ChessNetwork::startServer);
networkMenu->addAction(listenAction);
auto* connectAction = new QAction("&Connect...", this);
connect(connectAction, &QAction::triggered, this, [this]() {
QDialog* dialog = new QDialog(this);
QLineEdit* addressInput = new QLineEdit("Adresse : ");
QLineEdit* portInput = new QLineEdit("Port : ");
QPushButton* okButton = new QPushButton("Ok");
QHBoxLayout* layout = new QHBoxLayout();
layout->addWidget(addressInput);
layout->addWidget(portInput);
layout->addWidget(okButton);
dialog->setLayout(layout);
dialog->exec();
connect(okButton, &QPushButton::clicked, this, []() {
qDebug() << "button clicked";
});
connect(dialog, &QDialog::accepted, this, [this, addressInput, portInput]() {
chessNetwork_.connect(addressInput->text(), portInput->text().toInt());
});
});
networkMenu->addAction(connectAction);
menuBar()->addMenu(chessNetwork_.getNetworkMenu());
menuBar()->hide();
connect(&chessNetwork_, &network::ChessNetwork::doMove, this, &Window::networkPlay);
connect(&chessNetwork_, &network::ChessNetwork::invalidMove, this, &Window::cancelLastMove);
QTimer::singleShot(1, [this]() {
selectionWindow_ = new SelectionWindow(this);
......@@ -192,26 +168,26 @@ namespace frontend {
{
to->setColor("red");
QTimer::singleShot(400, to, &Tile::resetColor);
removeAllHighlight();
}
else
{
updateBoard(move->getAffectedCoordinates());
chessNetwork_.sendMove(network::Coordinate(from_->getPos().x, from_->getPos().y), network::Coordinate(to->getPos().x, to->getPos().y));
reloadPossibleMoves();
update(*move);
}
from_ = nullptr;
updateWindowTitle();
removeAllHighlight();
}
void Window::networkPlay(network::Coordinate from, network::Coordinate to)
{
const backend::Move* move = backend::Board::getInstance()->play(Coordinate(from.x, from.y), Coordinate(to.x, to.y));
update(*move);
}
updateBoard(move->getAffectedCoordinates());
reloadPossibleMoves();
updateWindowTitle();
removeAllHighlight();
void Window::cancelLastMove()
{
std::optional<backend::Move> move = backend::Board::getInstance()->cancelLastMove();
if (move) update(*move);
}
void Window::updateWindowTitle()
......@@ -276,6 +252,14 @@ namespace frontend {
highlightedTiles_.clear();
}
void Window::update(const backend::Move& move)
{
updateBoard(move.getAffectedCoordinates());
reloadPossibleMoves();
updateWindowTitle();
removeAllHighlight();
}
Tile* Window::getTileFromPos(QPoint pos)
{
return dynamic_cast<Tile*>(childAt(pos));
......
......@@ -22,7 +22,7 @@
namespace frontend {
class Tile;
class SelectionWindow;
class network::Coordinate;
struct network::Coordinate;
/**
* The main Window of the view.
......@@ -88,6 +88,8 @@ namespace frontend {
void networkPlay(network::Coordinate from, network::Coordinate to);
void cancelLastMove();
/**
* Updates the Window title depending on which player's turn it is.
*/
......@@ -117,6 +119,8 @@ namespace frontend {
*/
void removeAllHighlight();
void update(const backend::Move& move);
/**
* Returns a pointer to the Tile at the given position in the Window.
*/
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment