Jogo de Gomoku para Três Jogadores com Abordagem Orientada a Objetos em C++

Estrutura do Programa

A arquitetura do prograam baseia-se em classes que representam os componentes do jogo:

  • ChessBoard: Responsável por desenhar o tabuleiro do jogo, incluindo suas dimensões pesronalizáveis.
  • Player: Uma classe base abstrata para os jogadores. Dela derivam classes específicas para cada cor de peça (WhitePlayer, BlackPlayer, YellowPlayer). Cada jogador mantém uma lista de suas peças e possui um método para verificar se venceu.
  • ChessPiece: Representa uma única peça no tabuleiro, com atributos de posição (x, y) e cor.
  • Mouse: Captura e gerencia as interações do mouse, como cliques e suas coordenadas.
  • Menu: Encapsula a lógica principal do jogo, gerenciando a interface do usuário, o fluxo do jogo e as interações entre os jogadores e o tabuleiro.

A organização dos arquivos segue um padrão de separação entre declaração (.h) e implementação (.cpp) para cada classe principal.

Implementação do Código

main.cpp

O ponto de entrada do programa. Inicializa o objeto Menu e gerencia o loop principal da interface, direcionando para as funções de iniciar o jogo, exibir introdução ou sair.


#include <iostream>
#include <graphics.h>
#include "ChessBoard.h"
#include "Menu.h"
#include "Mouse.h"
#include "Player.h"

int main() {
   Menu mainMenu;
   while (true) {
       int choice = mainMenu.displayMainMenu();
       switch (choice) {
           case '1': // Iniciar Jogo
               mainMenu.startGame();
               break;
           case '2': // Introdução
               mainMenu.showIntroduction();
               break;
           case '0': // Sair
               return 0;
       }
   }
   return 0;
}
   

Player.h

Define a classe base Player e suas classes derivadas WhitePlayer, BlackPlayer e YellowPlayer.


#pragma once
#include <vector>
#include <algorithm>
#include "ChessPiece.h"
#include "Mouse.h"
#include "ChessBoard.h"

// Classe base abstrata para Jogadores
class Player {
private:
   std::vector<ChessPiece> playerPieces;

public:
   virtual ~Player() = default; // Destrutor virtual

   std::vector<ChessPiece>& getPieces();
   virtual void placePiece(Mouse& mouse, Player& other1, Player& other2) = 0; // Método puro para colocar peça
   bool checkWinCondition(const ChessBoard& board); // Verifica condição de vitória
};

// Jogador de Peças Brancas
class WhitePlayer : public Player {
public:
   void placePiece(Mouse& mouse, Player& other1, Player& other2) override;
};

// Jogador de Peças Pretas
class BlackPlayer : public Player {
public:
   void placePiece(Mouse& mouse, Player& other1, Player& other2) override;
};

// Jogador de Peças Amarelas
class YellowPlayer : public Player {
public:
   void placePiece(Mouse& mouse, Player& other1, Player& other2) override;
};
   

Player.cpp

Implementa os métodos da classe Player e suas derivadas, incluindo a lógica para colocar peças e verificar a condição de vitória (horizontal, vertical e diagonal).


#include "Player.h"

std::vector<ChessPiece>& Player::getPieces() {
   return this->playerPieces;
}

bool Player::checkWinCondition(const ChessBoard& board) {
   if (playerPieces.empty()) {
       return false;
   }
   const ChessPiece& lastPiece = playerPieces.back();
   int pieceX = lastPiece.getX();
   int pieceY = lastPiece.getY();
   int pieceColor = lastPiece.getColor();

   // Verifica direções horizontal, vertical e diagonais
   int directions[4][2] = {{1, 0}, {0, 1}, {1, 1}, {1, -1}}; // H, V, D\, D/
   for (auto& dir : directions) {
       int count = 1;
       // Verifica para frente
       for (int i = 1; i < 5; ++i) {
           int checkX = pieceX + dir[0] * i * BOARD_INTERVAL;
           int checkY = pieceY + dir[1] * i * BOARD_INTERVAL;
           ChessPiece target(checkX, checkY, pieceColor);
           if (std::find(playerPieces.begin(), playerPieces.end(), target) != playerPieces.end()) {
               count++;
           } else {
               break;
           }
       }
       // Verifica para trás
       for (int i = 1; i < 5; ++i) {
           int checkX = pieceX - dir[0] * i * BOARD_INTERVAL;
           int checkY = pieceY - dir[1] * i * BOARD_INTERVAL;
           ChessPiece target(checkX, checkY, pieceColor);
           if (std::find(playerPieces.begin(), playerPieces.end(), target) != playerPieces.end()) {
               count++;
           } else {
               break;
           }
       }
       if (count >= 5) {
           return true;
       }
   }
   return false;
}

// Função auxiliar para adicionar peça sem duplicatas e desenhar
void addUniquePiece(Player& currentPlayer, Player& other1, Player& other2, const ChessPiece& newPiece, Mouse& mouse) {
   auto isDuplicate = [&](const Player& p, const ChessPiece& piece) {
       return std::find(p.getPieces().begin(), p.getPieces().end(), piece) != p.getPieces().end();
   };

   if (!isDuplicate(currentPlayer, newPiece) && !isDuplicate(other1, newPiece) && !isDuplicate(other2, newPiece)) {
       currentPlayer.getPieces().push_back(newPiece);
       setfillcolor(newPiece.getColor());
       fillcircle(newPiece.getX(), newPiece.getY(), PIECE_RADIUS);
       mouse.incrementClickCount();
   }
}

void WhitePlayer::placePiece(Mouse& mouse, Player& other1, Player& other2) {
   if (mouse.isClicked()) {
       ChessPiece piece(mouse.getClickCoords().x, mouse.getClickCoords().y, WHITE);
       piece.alignToGrid();
       addUniquePiece(*this, other1, other2, piece, mouse);
   }
}

void BlackPlayer::placePiece(Mouse& mouse, Player& other1, Player& other2) {
   if (mouse.isClicked()) {
       ChessPiece piece(mouse.getClickCoords().x, mouse.getClickCoords().y, BLACK);
       piece.alignToGrid();
       addUniquePiece(*this, other1, other2, piece, mouse);
   }
}

void YellowPlayer::placePiece(Mouse& mouse, Player& other1, Player& other2) {
   if (mouse.isClicked()) {
       ChessPiece piece(mouse.getClickCoords().x, mouse.getClickCoords().y, YELLOW);
       piece.alignToGrid();
       addUniquePiece(*this, other1, other2, piece, mouse);
   }
}
   

ChessPiece.h

Define a classe ChessPiece, com métodos para obter e definir coordenadas e cor, além de um operador de igualdade para comparação.


#pragma once
#include <graphics.h>

#define BOARD_GRID_SIZE 20 // Tamanho do intervalo da grade
#define PIECE_RADIUS 5     // Raio da peça

// Classe para representar uma peça
class ChessPiece {
private:
   int posX;
   int posY;
   int pieceColor;
public:
   ChessPiece(int x = 0, int y = 0, int color = WHITE) : posX(x), posY(y), pieceColor(color) {}

   bool operator==(const ChessPiece& other) const; // Sobrecarga do operador ==

   void setColor(int color);
   void setPosition(int x, int y);

   int getX() const { return posX; }
   int getY() const { return posY; }
   int getColor() const { return pieceColor; }

   void alignToGrid(); // Alinha a peça à grade mais próxima e a desenha
};
   

ChessPiece.cpp

Implementa os métodos de ChessPiece, incluindo o alinhamento da peça às coordenadas da grade.


#include "ChessPiece.h"

bool ChessPiece::operator==(const ChessPiece& other) const {
   return this->posX == other.posX && this->posY == other.posY;
}

void ChessPiece::setColor(int color) {
   this->pieceColor = color;
}

void ChessPiece::setPosition(int x, int y) {
   this->posX = x;
   this->posY = y;
}

void ChessPiece::alignToGrid() {
   // Ajusta X à grade mais próxima
   if (posX % BOARD_GRID_SIZE > BOARD_GRID_SIZE / 2) {
       posX = (posX / BOARD_GRID_SIZE + 1) * BOARD_GRID_SIZE;
   } else {
       posX = (posX / BOARD_GRID_SIZE) * BOARD_GRID_SIZE;
   }
   // Ajusta Y à grade mais próxima
   if (posY % BOARD_GRID_SIZE > BOARD_GRID_SIZE / 2) {
       posY = (posY / BOARD_GRID_SIZE + 1) * BOARD_GRID_SIZE;
   } else {
       posY = (posY / BOARD_GRID_SIZE) * BOARD_GRID_SIZE;
   }
   
   // Desenha a peça após o alinhamento
   setfillcolor(pieceColor);
   fillcircle(posX, posY, PIECE_RADIUS);
}
   

ChessBoard.h

Define a classe ChessBoard, com métodos para obter e definir dimensões e para desenhar o tabuleiro.


#pragma once
#include <graphics.h>

#define BOARD_GRID_SIZE 20 // Intervalo da grade

// Classe para representar o tabuleiro
class ChessBoard {
private:
   int boardWidth;
   int boardHeight;
public:
   ChessBoard(int w = 400, int h = 400) : boardWidth(w), boardHeight(h) {}

   void setDimensions(int w, int h);
   int getWidth() const;
   int getHeight() const;
   void drawBoard(); // Desenha o tabuleiro
};
   

ChessBoard.cpp

Implementa os métodos de ChessBoard, incluindo a lógica de desenho das linhas da grade.


#include "ChessBoard.h"

void ChessBoard::setDimensions(int w, int h) {
   this->boardWidth = w;
   this->boardHeight = h;
}

int ChessBoard::getWidth() const {
   return boardWidth;
}

int ChessBoard::getHeight() const {
   return boardHeight;
}

void ChessBoard::drawBoard() {
   for (int i = 0; i <= boardWidth; i += BOARD_GRID_SIZE) {
       line(0, i, boardWidth, i); // Linhas horizontais
       line(i, 0, i, boardHeight); // Linhas verticais
   }
}
   

Mouse.h

Define a classe Mouse para capturar eventos do mouse.


#pragma once
#include <graphics.h>

// Classe para gerenciar eventos do mouse
class Mouse {
private:
   ExMessage mouseEvent;
   int clickCount;
public:
   Mouse() : clickCount(0) {}

   void incrementClickCount();
   int getClickCount() const;
   ExMessage& getMouseEvent();
   bool isClicked(); // Verifica se o botão esquerdo do mouse foi clicado
   POINT getClickCoords() const; // Retorna as coordenadas do clique
};
   

Mouse.cpp

Implementa os métodos de Mouse, verificando cliques e capturando coordenadas.


#include "Mouse.h"

void Mouse::incrementClickCount() {
   this->clickCount++;
}

int Mouse::getClickCount() const {
   return this->clickCount;
}

ExMessage& Mouse::getMouseEvent() {
   return this->mouseEvent;
}

bool Mouse::isClicked() {
   if (peekmessage(&this->mouseEvent) && this->mouseEvent.message == WM_LBUTTONDOWN) {
       return true;
   }
   return false;
}

POINT Mouse::getClickCoords() const {
   return {mouseEvent.x, mouseEvent.y};
}
   

Menu.h

Define a classe Menu, responsável pelas interfaces e pelo fluxo principal do jogo.


#pragma once
#include <graphics.h>
#include <conio.h>
#include "ChessBoard.h"
#include "Mouse.h"
#include "Player.h"

// Classe que gerencia as interfaces e o fluxo do jogo
class Menu {
private:
   void handlePlayerTurns(Mouse& mouse, WhitePlayer& wp, BlackPlayer& bp, YellowPlayer& yp);
   bool checkGameEnd(const ChessBoard& board, WhitePlayer& wp, BlackPlayer& bp, YellowPlayer& yp);

public:
   int displayMainMenu(); // Exibe o menu principal
   void showIntroduction(); // Exibe a tela de introdução
   void startGame(); // Inicia o loop do jogo
};
   

Menu.cpp

Implementa os métodos de Menu, incluindo a inicialização do gráfico, desenho das interfaces e gerenciamento dos turnos dos jogadores.


#include "Menu.h"

int Menu::displayMainMenu() {
   initgraph(800, 600);
   setbkcolor(WHITE);
   cleardevice();
   settextstyle(70, 0, "Arial");
   outtextxy(150, 120, "Gomoku 3 Jogadores");
   settextstyle(50, 0, "Arial");
   outtextxy(280, 250, "1. Jogar");
   outtextxy(280, 350, "2. Instruções");
   outtextxy(280, 450, "0. Sair");

   int key;
   while (true) {
       if (_kbhit()) {
           key = _getch();
           if (key == '1' || key == '2' || key == '0') {
               closegraph();
               return key;
           }
       }
   }
}

void Menu::showIntroduction() {
   initgraph(800, 600, 0);
   cleardevice();
   settextstyle(50, 0, "Arial");
   outtextxy(0, 100, "Instruções:");
   outtextxy(0, 200, "- Jogo para 3 jogadores.");
   outtextxy(0, 300, "- Ordem: Branco, Preto, Amarelo.");
   outtextxy(0, 400, "- Alcance 5 peças em linha para vencer.");
   outtextxy(450, 500, "Pressione 0 para voltar.");
   while (!_kbhit() || _getch() != '0');
   closegraph();
}

void Menu::startGame() {
   ChessBoard board(600, 600); // Define um tabuleiro 600x600
   Mouse mouse;
   WhitePlayer whitePlayer;
   BlackPlayer blackPlayer;
   YellowPlayer yellowPlayer;

   initgraph(board.getWidth(), board.getHeight());
   setbkcolor(RGB(245, 222, 181)); // Cor de fundo
   cleardevice();
   board.drawBoard();

   while (true) {
       // Verifica se o jogador pode jogar
       if (mouse.isClicked()) {
            handlePlayerTurns(mouse, whitePlayer, blackPlayer, yellowPlayer);
       }
      
       // Verifica condição de vitória
       if (checkGameEnd(board, whitePlayer, blackPlayer, yellowPlayer)) {
           // Espera o jogador pressionar '0' para retornar ao menu
           while (!_kbhit() || _getch() != '0'); 
           break;
       }
        // Adiciona um pequeno delay para não sobrecarregar a CPU
       Sleep(10); 
   }
   closegraph();
}

void Menu::handlePlayerTurns(Mouse& mouse, WhitePlayer& wp, BlackPlayer& bp, YellowPlayer& yp) {
   int turn = mouse.getClickCount() % 3;
   switch (turn) {
       case 0: wp.placePiece(mouse, bp, yp); break;
       case 1: bp.placePiece(mouse, wp, yp); break;
       case 2: yp.placePiece(mouse, wp, bp); break;
   }
}

bool Menu::checkGameEnd(const ChessBoard& board, WhitePlayer& wp, BlackPlayer& bp, YellowPlayer& yp) {
   if (wp.checkWinCondition(board)) {
       cleardevice();
       settextstyle(30, 0, "Arial");
       outtextxy(board.getWidth() / 2 - 100, board.getHeight() / 2, "Branco Venceu! Pressione 0 para voltar.");
       return true;
   } else if (bp.checkWinCondition(board)) {
       cleardevice();
       settextstyle(30, 0, "Arial");
       outtextxy(board.getWidth() / 2 - 100, board.getHeight() / 2, "Preto Venceu! Pressione 0 para voltar.");
       return true;
   } else if (yp.checkWinCondition(board)) {
       cleardevice();
       settextstyle(30, 0, "Arial");
       outtextxy(board.getWidth() / 2 - 100, board.getHeight() / 2, "Amarelo Venceu! Pressione 0 para voltar.");
       return true;
   }
   return false;
}
   

Efeitos de Implementação

O jogo exibe uma interface gráfica onde os jogadores podem clicar para colocar suas peças. O tabuleiro é desenhado com linhas de grade, e as peças são centralizadas nas interseções. A detecção de vitória é realizada após cada jogada.

Bugs Conhecidos

  • A detecção de vitória pode não cobrir todas as combinações em casos extremos.
  • A interface de usuário pode ser aprimorada para melhor feedback visual.

Tags: C++ Programação Orientada a Objetos Jogo Gomoku Gráficos em C++ Desenvolvimento de Jogos

Publicado em 6-14 19:40 por Thomas