Search code examples
c++multiple-definition-error

Error multiple definition of struct, but i dont see the error anywhere ( C++ )


made a struct, compiled my program, worked perfectly, didn't change anything, closed vm, restarted vm and vsc, and then did make in console again and it came up with this error

1 warning generated.
linking build/main.o
build/Game.o:(.bss+0x60): multiple definition of `scoreArray'
build/main.o:(.bss+0x10): first defined here
clang-5.0: error: linker command failed with exit code 1 (use -v to see 
invocation)
Makefile:33: recipe for target 'build/2048' failed
make: *** [build/2048] Error 1

Here is my code, i cant see anything thats redefining it, please someone spot it for me, here is the header file and the source file that its used in

main.cpp

/**
 * File: main.cpp
 * Author: not lettin u know :)
 * Date: 20-11-2019
 * Desc:
 * Copyright: University of West of England 2017
 */
#include <stdio.h>
#include <cstdio>
#include <stdlib.h>
// include the UFCFGL301's standard library
#include <ufcfgl-30-1.h>
#include <iostream>
#include <time.h>
#include "Grid.h"
#include "Game.h"
// uncomment if you want to use the graphics library
#include <graphics.h>

using namespace std;
using namespace uwe;

/**
 * main routine, implements ...
 * @return      success
 */
int main(void) {

    srand(time(0));
    Game game;
// -------Initialising Game-------
    while(1){

        game.chooseFunction();
        grid.totalScore = 0;
        grid.initGrid();
        grid.addValue();
        cout << "\033[2J"; // this clears the terminal
        cout << "\nPlayer Score : " << grid.totalScore << "\n";
        std::cout << "\nMoves = " << grid.moves << "\n\nlast input = N/A \n";
        grid.setScore(grid.score);
        grid.drawGrid();


    // This launches graphics window where all keypresses are inputted
    // this is also where we start the lambda functions in a loop 

        initialiseGraphics(600,400);
        loop (
            [&](){
            },

            [&](keyPress keyPress){

    // w a s d are also inputted into an integer as their respective ascii value
    // so this allows the use of w a s d and also the arrow keys in controlling
    // the moves of the player

                int kpVal = keyPress.getKeyCode();

                if ( kpVal == 'q' ){
                    return true;
                }
                if ( kpVal == 'w' || kpVal == keyPress::upKey ){
                    cout << "\033[2J"; // this clears the terminal
                    grid.shiftUp();
                    cout << "Player Score : " << grid.totalScore << "\n";
                    std::cout << "\nMoves = " << grid.moves 
                              << "\n\nlast input = UP \n";
                    grid.setScore(grid.score);
                    grid.drawGrid();
                    game.checkGameState();
                    if(game.gameFinished == true){
                        return true;
                    }
                }
                if ( kpVal == 'a' || kpVal == keyPress::leftKey ){
                    cout << "\033[2J"; // this clears the terminal
                    grid.shiftLeft();
                    cout << "Player Score : " << grid.totalScore << "\n";
                    std::cout << "\nMoves = " << grid.moves  
                              << "\n\nlast input = LEFT \n";
                    grid.setScore(grid.score);
                    grid.drawGrid();
                    game.checkGameState();
                    if(game.gameFinished == true){
                        return true;
                    }
                }
                if ( kpVal == 's' || kpVal == keyPress::downKey ){
                    cout << "\033[2J"; // this clears the terminal
                    grid.shiftDown();
                    cout << "Player Score : " << grid.totalScore << "\n";
                    std::cout << "\nMoves = " << grid.moves 
                              << "\n\nlast input = DOWN \n";
                    grid.setScore(grid.score);
                    grid.drawGrid();
                    game.checkGameState();
                    if(game.gameFinished == true){
                        return true;
                    }
                }
                if ( kpVal == 'd' || kpVal == keyPress::rightKey ){
                    cout << "\033[2J"; // this clears the terminal
                    grid.shiftRight();
                    cout << "Player Score : " << grid.totalScore << "\n";
                    std::cout << "\nMoves = " << grid.moves 
                              << "\n\nlast input = RIGHT \n";
                    grid.setScore(grid.score);
                    grid.drawGrid();
                    game.checkGameState();
                    if(game.gameFinished == true){
                        return true;
                    }
                }
                return false;
            }
        );
    }
}

Game.h

#pragma once
#include <cstdio>
#include <string>
#include <graphics.h>
#include "Grid.h"

class Game{

public:

    bool canMove();
    bool checkWin();
    bool checkLoss();
    void checkGameState();
    void chooseFunction();
    void printHighScores();
    void writeHighScore();

    bool gameFinished = false;

private:

};
extern Game game;

struct highScore {
    int score;
    std::string name;
};   

Game.cpp

#include "Grid.h"
#include "Game.h"
#include <sstream>
#include <unistd.h>
#include <iostream>
#include <cstdio>
#include <graphics.h>
#include <fstream>
#include <string>
#include <algorithm> //std::sort for array of structs sorting based on struct
                     // integer

Grid grid;
highScore scoreArray[100];

// functions to check game state for win or loss or can make moves

bool Game::canMove(){
    bool canMove = false;
    for(int x = 0; x < Grid::tilesWide; x++){
        for(int y = 0; y < Grid::tilesHigh; y++){
            if (grid.grid[x][y] == Grid::emptyCell) {
                canMove = true;
                return canMove;
            }

// multiple if statements due to if grid[3][1] was being checked with
// grid[3+1][1] then it would loop around and check against grid[1][2] 
// for an unknown reason, so to combat this i had to check specifically against
// the edge of the board 

            else if(y == 3 && x < 3){
                if( grid.grid[x][y] == grid.grid[x+1][y] ){
                    canMove = true;
                    return canMove;
                }
            }
            else if(x == 3 && y < 3){
                if( grid.grid[x][y] == grid.grid[x][y+1] ){
                    canMove = true;
                    return canMove;
                }
            }
            else if( ( x < 3 && y < 3 ) && 
                    (grid.grid[x][y] == grid.grid[x][y+1] ||
                     grid.grid[x][y] == grid.grid[x+1][y]) ) {

                canMove = true;
                return canMove;
            }
        }
    }
    return canMove;
}

bool Game::checkWin(){
    bool win = false;
    for(int x = 0; x < Grid::tilesWide; x++){
        for(int y = 0; y < Grid::tilesHigh; y++){
            if(grid.grid[x][y] == 2048){
                win = true;
                return win;
            }
        }
    }
    return win;
}

bool Game::checkLoss(){
    bool loss = false;
    if(canMove() == false ){
        loss = true;
        return loss;
    }
    return loss;
}

void Game::checkGameState(){
    if(checkWin() == true) {
        std::cout << "\n\n--------YOU WIN--------\n\n";
        gameFinished = true;
        writeHighScore();
    }
    else if(checkLoss() == true) {
        std::cout << "\n\n--------YOU LOSE--------\n\n";
        gameFinished = true;
    }
}

// functions for initializing game/options

void Game::chooseFunction(){
    while(1){
        int function = 0;
        std::cout << "\nPlease Choose A Function\n1) Play Game\n2) High Scores"
                  << "\n3) Exit Program";
        std::cout << "\nFunction : ";
        std::cin  >> function;
        if (std::cin.fail() || function < 1 || function > 3) {
            std::cin.clear();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
            std::cin.ignore(1000,'\n'); // this is to stop the validation from
                                   // looping for each wrong character entered
            std::cout << "Not a valid option, Please re-enter \n\n" ;
            sleep(1); 

        }
        else if (function == 1) {
            std::cout << "------Launching Game------\n\n";
            std::cout << "---------LOADING---------\n";
            for(int x=0;x < 5;x++){
                std::cout << "|||||" << std::flush;
                sleep(1);
            }
            return ;
        }

        else if (function == 2) {
            std::cout << "---Printing High Scores---\n";
            std::cout << "---------LOADING---------\n";
            for(int x=0;x < 5;x++){
                std::cout << "|||||" << std::flush;
                sleep(1);
            }
            printHighScores();
            sleep(10);
        }

        else if (function == 3) {
            std::cout << "-------Now Exiting------- \n\n" << std::flush ;
            std::cout << "---------LOADING---------\n";
            for(int x=0;x < 5;x++){
                std::cout << "|||||" << std::flush;
                sleep(1);
            }
            exit(0);
        }

    }
}

// This function is called upon a win, and writes the high score ,of those who
// win the game, into a text file under the format of  SCORE - NAME

void Game::writeHighScore(){
    std::string name;
    std::cout << "\nEnter Name To Store High Score ( Max 16 Characters )\n"
              << "Name : ";
    std::getline(std::cin,name);
    // this is to stop the string being over 16 characters
    if(std::cin.fail() || name.length() > 16){
        std::cout << "\n\nPlease Enter A Max of 16 Characters\n\n";
        writeHighScore();
        return;
    }
    else{
// This opens the output stream to the high scores text file
// and immediately points to the next free line in the file
        std::ofstream HighScore;
        HighScore.open ("HighScores.txt", std::ios::app);
        HighScore << grid.totalScore << "   "<< name << std::endl; 
        HighScore.close();
        return;
    }
}

// simple comparator function to be used with std::sort in order to sort
// the array of structs containing scores and their respective player names

bool sortScore(highScore a,highScore b){
    return a.score > b.score; 
} 

// this function prints the high scores from the text file, however
// im not sure how to sort a text file each time its appended, so instead
// when this function is called, it seperates the lines of the files into
// the score and their respective player name, these are stored in a struct
// object for each line, and then the array of structs is sorted using
// std::sort and the comparator function and then the first 10 scores and names
// in the struct array are printed, TLDR, prints Top 10 scores :) 
// 
// not on the spec for the assignment but i enjoyed learning about how
// i would go about making something like this :) 

void Game::printHighScores() { // function to choose and print ascii art
    std::ifstream highScores;
    highScores.open("HighScores.txt");
    std::string line;
    int i=0;
    if(!highScores){
        std::cout << "\nCould Not Open HighScores.txt";
        sleep(2);
        return;
    }
    else{
        while(getline(highScores, line)){
            std::istringstream iss(line);
            int score;
            std::string name;

// this seperates out the score and the name string from each line in the file
            if(!(iss >> score && iss.ignore() && std::getline(iss,name))){
                break;
            }
// this adds to the array of structs defined in the header file
            scoreArray[i].score = score;
            scoreArray[i].name = name;
            i++;
        }
        std::sort(&scoreArray[0],&scoreArray[100],sortScore);
    }

// and finally... this prints the top 10 scores

    std::cout << "\n\n-------HIGH SCORES-------\n\n";
    for(int i=0; i<10; i++){
        std::cout <<i+1<<")  "<<scoreArray[i].score<<" - "<<scoreArray[i].name
        <<"\n";
    }
}

Solution

  • The line

    highScore scoreArray[100]; 
    

    in your header declares a variable whenever this header is included. I assume that header is included by multiple source files.

    Move this into a .cpp and add an extern for it to the header.

    Basically, for each source file that includes this header, you're declaring a variable called scoreArray. Each .cpp that includes this header will declare its own scoreArray. The linker, when doing its linking thing, will encounter two variables with the same name and can't decide which one is "correct," so it gives up and generates the error that you're seeing.

    So, you need to move the definition into a .cpp and just reference it as an extern in the header. (There are a few different approaches to this. Choose whichever suits you best.)