Skip to content

Instantly share code, notes, and snippets.

@nathanielanozie
Last active June 26, 2024 03:09
Show Gist options
  • Save nathanielanozie/3c77ab76570b81044e77ef2ac2185f98 to your computer and use it in GitHub Desktop.
Save nathanielanozie/3c77ab76570b81044e77ef2ac2185f98 to your computer and use it in GitHub Desktop.
c++ opengl arithmetic text game
#include <iostream>
#include <random>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <ctime>
#include <sstream>
#include "core.h"
std::vector<std::string> stringToVector(std::string input)
{
//convert a string to a vector of strings. splitting by \n
std::vector<std::string> result;
std::stringstream myStream(input);
std::string line;
while(std::getline(myStream, line, '\n'))
{
result.push_back(line);
}
return result;
}
//get a random integer in given range
int getRandomNumInRange(int a, int b)
{
std::random_device randDev;
std::mt19937 gen(randDev());
std::uniform_int_distribution<int> unif(a,b);
return unif(gen);
}
//constructor
ArithmeticGameProblem::ArithmeticGameProblem(int min, int max, ArithmeticType gameType)
{
mMin = min;
mMax = max;
mArithmeticType = gameType;
//populate choices
mChoices.push_back('a');
mChoices.push_back('b');
mChoices.push_back('c');
mAnswerUser = 0; //garbage value it should be an actual answer
}
bool ArithmeticGameProblem::getIsChoiceCorrect(char userChoice)
{
//get whether users choice is correct
getUserAnswer(userChoice);
return isCorrect();
}
//returns the problem for this object
std::string ArithmeticGameProblem::getProblem()
{
return mProblem;
}
std::string ArithmeticGameProblem::getSolutions()
{
return mSolutionsText;
}
//produces random problem
std::string ArithmeticGameProblem::computeProblem()
{
std::string result = "";
result += "yaaay problem:\n";
mFirstNum = getRandomNumInRange(mMin, mMax);
mSecondNum = getRandomNumInRange(mMin, mMax);
//print proper symbol depending on game type
switch(mArithmeticType)
{
case kMultiplication:
result += "" + std::to_string(mFirstNum) + " "+ "X" + " " + std::to_string(mSecondNum) + "\n\n";
break;
case kDivision:
result += "" + std::to_string(mFirstNum) + " "+ "/" + " " + std::to_string(mSecondNum) + "\n\n";
break;
case kAddition:
result += "" + std::to_string(mFirstNum) + " "+ "+" + " " + std::to_string(mSecondNum) + "\n\n";
break;
case kSubtraction:
result += "" + std::to_string(mFirstNum) + " "+ "-" + " " + std::to_string(mSecondNum) + "\n\n";
break;
default:
result += "" + std::to_string(mFirstNum) + " "+ "X" + " " + std::to_string(mSecondNum) + "\n\n";
break;
}
//save problem
mProblem = result;
return result;
}
//produces random solution
std::string ArithmeticGameProblem::computeSolutions()
{
std::string result = "";
double answer = getAnswer();
mSolutions.push_back(answer);
double randomAnswerA = answer + getRandomNumInRange(1, 10);
double randomAnswerB = answer - getRandomNumInRange(1, 10);
mSolutions.push_back(randomAnswerA);
mSolutions.push_back(randomAnswerB);
//shuffle the answers
std::random_device rd;
std::mt19937 genB(rd());
std::shuffle(mSolutions.begin(), mSolutions.end(), genB);
//print answers
for(unsigned int i=0; i < mSolutions.size(); ++i)
{
std::string choice(1, mChoices[i]); //convert char to string
result += "" + choice + " " + std::to_string(mSolutions[i]) + "\n";
}
//save solution
mSolutionsText = result;
return result;
}
void ArithmeticGameProblem::getUserAnswer(char userChoice)
{
/*
//get user choice
char choice;
std::cout << "enter choice a, b, or c" << "\n";
std::cin >> choice;
*/
printf("testing answer\n");
double answerUser;
for(unsigned int j=0; j < mChoices.size(); ++j)
{
if(mChoices[j] == userChoice)
{
printf("checking\n");
std::cout<<"test:"<<mSolutions[0];
mAnswerUser = mSolutions[j];
printf("getting solution\n");
}
}
}
bool ArithmeticGameProblem::printResult() const
{
std::cout << "your answer " << mAnswerUser << "\n";
double answer = getAnswer();
if(mAnswerUser != answer)
{
std::cout << "please try again on a new problem\n";
std::cout << "the correct answer was " << answer << "\n";
return false;
}
std::cout << "great job! you're doing great :) \n";
return true;
}
bool ArithmeticGameProblem::isCorrect() const
{
return mAnswerUser == getAnswer();
}
double ArithmeticGameProblem::getAnswer() const
{
double result;
switch(mArithmeticType)
{
case kMultiplication:
result = mFirstNum * mSecondNum;
break;
case kDivision:
result = mFirstNum / mSecondNum;
break;
case kAddition:
result = mFirstNum + mSecondNum;
break;
case kSubtraction:
result = mFirstNum - mSecondNum;
break;
default:
result = mFirstNum * mSecondNum;
break;
}
return result; //specific for multiplication
}
/*
void ArithmeticGameProblem::doIt()
{
computeProblem();
computeSolutions();
getUserAnswer();
printResult();
}
*/
ArithmeticType getGameType()
{
//print possible game types
std::cout << "pick game type" << "\n";
std::cout << "(a) Multiplication" << "\n";
std::cout << "(b) Division" << "\n";
std::cout << "(c) Addition" << "\n";
std::cout << "(d) Subtraction" << "\n";
//get user choice
char choice;
std::cout << "enter choice a, b, c or d" << "\n";
std::cin >> choice;
ArithmeticType result;
switch(choice)
{
case 'a':
result = kMultiplication;
break;
case 'b':
result = kDivision;
break;
case 'c':
result = kAddition;
break;
case 'd':
result = kSubtraction;
break;
default:
result = kMultiplication;
break;
}
return result;
}
/*
int main()
{
int numProblems = 2;
ArithmeticType gameType;// = kMultiplication;
gameType = getGameType();
ArithmeticGame game(numProblems, 1, 10, gameType);//number problems, minimimum number to multiply, maximum number to multiply, game type
//game.play();
//game.printGameResult();
//print data for a single question
unsigned int problemIndex = 0;
std::string questionData = game.getQuestionData(problemIndex); //need to print this to opengl window
std::cout << questionData << "\n";
char userChoice = 'a';
std::cout << game.getIsAnswerCorrect(problemIndex, userChoice) << "\n";
return 0;
}
*/
#ifndef CORE_H
#define CORE_H
std::vector<std::string> stringToVector(std::string);
enum ArithmeticType
{
kMultiplication,
kDivision,
kAddition,
kSubtraction
};
class ArithmeticGameProblem
{
/*responsible for integer multiplication text game
example usage:
ArithmeticGameProblem problem(1, 10); //minimimum number to multiply, maximum number to multiply
problem.doIt();
problem.printResult();
*/
public:
ArithmeticGameProblem(int, int, ArithmeticType); //excepts range for numbers in problem
void doIt(); //called by client to handle this game problem
bool printResult() const;
bool isCorrect() const; //is client answer correct
std::string getProblem(); //get the problem for this object
std::string getSolutions(); //get the solutions for this object
std::string computeProblem();
std::string computeSolutions();
bool getIsChoiceCorrect(char userChoice);
private:
void getUserAnswer(char userChoice);
double getAnswer() const;
private:
double mFirstNum;
double mSecondNum;
ArithmeticType mArithmeticType;
double mMin; //min number allowed
double mMax; //max number allowed
std::string mProblem; //what is the problem for this object
std::string mSolutionsText; //what are the solutions for this object
double mAnswerUser; //users answer value to problem
std::vector<double> mSolutions; //holds solutions
std::vector<char> mChoices; //choices for answers
};
class ArithmeticGame
{
/* example usage:
ArithmeticGame game(5, 1, 10);//number problems, minimimum number to multiply, maximum number to multiply
game.play();
game.printGameResult();
*/
public:
ArithmeticGame(int numProblems=10, int minNum=1, int maxNum=10, ArithmeticType gameType=kMultiplication)
{
for(unsigned int i=0; i<numProblems; i++)
{
ArithmeticGameProblem item(1, 10, gameType); //todo use inputs
mGameProblems.push_back(item);
}
mNumProblemsCorrect = 0;
mGameTimeSeconds = 0;
}
~ArithmeticGame(){}
std::string getQuestionForProblem(unsigned int problemIndex)
{
return mGameProblems[problemIndex].getProblem();
}
std::string getSolutionForProblem(unsigned int problemIndex)
{
return mGameProblems[problemIndex].getSolutions();
}
std::string computeQuestionData(unsigned int problemIndex)
{
if(problemIndex > getNumberProblems())
{
std::cout << "invalid problem index" << "\n";
return "";
}
std::string result = "";
result += mGameProblems[problemIndex].computeProblem();
result += mGameProblems[problemIndex].computeSolutions();
return result;
}
bool getIsAnswerCorrect(unsigned int problemIndex, char userChoice)
{
if(problemIndex > getNumberProblems())
{
std::cout << "invalid problem index" << "\n";
return 1;
}
std::string choice(1, userChoice);
std::cout << "user choice:" + choice + "\n";
//std::cout << "debugging getIsAnswerCorrect \n";
std::cout << "number problems:" << getNumberProblems() <<"\n";
std::cout << "testing problem >>>\n" << mGameProblems[problemIndex].getProblem();
std::cout << "testing solution >>>\n" << mGameProblems[problemIndex].getSolutions();
bool result = mGameProblems[problemIndex].getIsChoiceCorrect(userChoice);
//std::cout << "debugging getIsAnswerCorrect \n";
return result;
}
int getNumberCorrect()
{
std::vector<ArithmeticGameProblem>::iterator it;
for( it=mGameProblems.begin(); it<mGameProblems.end(); ++it)
{
if((*it).isCorrect()){ mNumProblemsCorrect += 1; }
}
return mNumProblemsCorrect;
}
float getPercentCorrect()
{
getNumberCorrect();//first figure out number correct before computing percent correct
return float(mNumProblemsCorrect)*100/float(mGameProblems.size());
}
/*
void incrementNumberCorrect()
{
mNumProblemsCorrect += 1;
}
int getNumberCorrect()
{
return mNumProblemsCorrect;
}
*/
/*
//play the game. presents a series of problems to play
void play()
{
std::time_t startTime = std::time(NULL);
std::vector<ArithmeticGameProblem>::iterator it;
for( it=mGameProblems.begin(); it<mGameProblems.end(); ++it)
{
(*it).doIt();
}
//keep track of total time for game
mGameTimeSeconds = (double)(std::time(NULL) - startTime);
}
*/
/*
void printGameResult()
{
std::cout << "Great playing!!!" << "\n\n";
std::cout << "number correct: " << getNumberCorrect() << "\n";
std::cout << "number problems: " << getNumberProblems() << "\n";
std::cout << "percent score: " << getPercentCorrect() << "\n";
//todo print time it took to answer questions
std::cout << "game time in seconds: " << mGameTimeSeconds << "\n";
}
*/
private:
int getNumberProblems() const
{
return mGameProblems.size();
}
private:
std::vector<ArithmeticGameProblem> mGameProblems; //holds all the problems for this game
int mNumProblemsCorrect;
double mGameTimeSeconds;
};
#endif
//pick between 5 or 10 questions.
//example of showing text in opengl and accepting keyboard input
//
//please modify/use at your own risk
//to compile
//g++ -framework OpenGL -framework GLUT -o out main.cpp core.cpp -Wno-deprecated-declarations
//
//to run
//./out
#ifdef __APPLE_CC__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include<iostream>
#include <string.h>
#include <random>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <ctime>
#include "core.h"
//holds enum for choices
enum Choices
{
kNull,
kNextQuestion,
kChoiceA,
kChoiceB,
kChoiceC
};
ArithmeticGame *game; //holds the game
std::string questionData = "test question data"; //this needs to be global as it is changed from keypress and we want in display it not to change
ArithmeticType gameType = kMultiplication; //game type for game to play
bool gametypeChosen = false; //has game type been chosen
float pctCorrect = 0; //what is percent correct on game
int tries = 0; //number of tries at the question
Choices choice = kNull; //this will hold current choice letter by user. start as null because nothing chosen yet
unsigned int questionIndex = 0; //this is the question index of problem user is facing
bool currentQuestionCorrect = false; //is current question correct
int numQuestions; //this is how many questions to answer
bool gameStarted = false; //this is to know whether to init the game
bool gameOver = false;
std::string problem; //testing
std::string solution;
char choiceToChar(Choices choice)
{
if(choice == kChoiceA)
{
return 'a';
}
else if(choice == kChoiceB)
{
return 'b';
}
else if(choice == kChoiceC)
{
return 'c';
}
else{
return 'a';
}
}
void initGame(int maxQuestions, ArithmeticType gameType)
{
//initialize the game here
numQuestions = maxQuestions;
std::cout << "numQuestions "<< numQuestions << "\n";
std::cout << kMultiplication << "\n";
//testing
//
//ArithmeticGame game(3, 1, 10, kMultiplication);
game = new ArithmeticGame(numQuestions, 1, 10, gameType);//number problems, minimimum number to multiply, maximum number to multiply, game type
for(unsigned int i=0; i<numQuestions; i++)
{
//compute question data for all problems here
//this way the data is available for entirety of game
game->computeQuestionData(i);
}
/*
problem = game->getQuestionForProblem(questionIndex);
solution = game->getSolutionForProblem(questionIndex);
questionData = problem + solution;
questionIndex += 1;
*/
}
void drawWord(void *font, const char *s, float x, float y)
{
unsigned int i;
glRasterPos2f(x, y);
for(i=0; i < strlen(s); i++)
{
glutBitmapCharacter(font, s[i]);
}
}
void possiblyInitGame(unsigned int numQuestions, ArithmeticType gameType)
{
if(!gameStarted)
{
gameStarted = true;
initGame(numQuestions, gameType);
//so we show first problem
problem = game->getQuestionForProblem(questionIndex);
solution = game->getSolutionForProblem(questionIndex);
questionData = problem + solution;
}
}
void displayQuestionData()
{
std::vector<std::string> questionDataVector = stringToVector(questionData);
std::vector<std::string>::iterator it;
int lines = 0;
for(it=questionDataVector.begin(); it<questionDataVector.end(); ++it)
{
const char *questionInfo = (*it).c_str();
drawWord(GLUT_BITMAP_HELVETICA_18, questionInfo, 80, 70+(20*lines)); //separate by 10 pixels
lines += 1;
}
//drawWord(GLUT_BITMAP_HELVETICA_18, questionInfo, 80, 50);
}
bool displayAnswerResult()
{
if(!gameStarted)
{
return false;
}
if(currentQuestionCorrect)
{
drawWord(GLUT_BITMAP_HELVETICA_18, "Correct!", 80, 350);
}
else{
drawWord(GLUT_BITMAP_HELVETICA_18, "try again on next question", 80, 330);
}
return true;
}
bool setCorrectQuestion(unsigned int questionIndex, Choices choice)
{
if(!gameStarted)
{
return false;
}
if(gameOver)
{
return false;
}
if(game->getIsAnswerCorrect(questionIndex, choiceToChar(choice)))
{
currentQuestionCorrect = true;
}
else{
currentQuestionCorrect = false;
}
return true;
}
void replayGame()
{
questionData = "test question data"; //this needs to be global as it is changed from keypress and we want in display it not to change
gameType = kMultiplication; //game type for game to play
gametypeChosen = false; //has game type been chosen
pctCorrect = 0; //what is percent correct on game
tries = 0; //number of tries at the question
choice = kChoiceA;//int choice = 0; //this will hold current choice letter by user
questionIndex = 0; //this is the question index of problem user is facing
currentQuestionCorrect = false; //is current question correct
numQuestions = 1; //this is how many questions to answer
gameStarted = false; //this is to know whether to init the game
gameOver = false;
}
//free game memory
void freeResources()
{
delete game;
}
void keyPress(unsigned char key, int x, int y)
{
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
glColor3f(0, 0, 0);
switch(key)
{
case '1':
std::cout << "1 pressed\n";
if(!gametypeChosen)
{
gameType = kMultiplication;
gametypeChosen = true;
}
break;
case '2':
std::cout << "2 pressed\n";
if(!gametypeChosen)
{
gameType = kDivision;
gametypeChosen = true;
}
break;
case '3':
std::cout << "3 pressed\n";
if(!gametypeChosen)
{
gameType = kAddition;
gametypeChosen = true;
}
break;
case '4':
std::cout << "4 pressed\n";
if(!gametypeChosen)
{
gameType = kSubtraction;
gametypeChosen = true;
}
break;
case 'p':
//for replaying game
if(!gameOver){break;}//do nothing if game is not over
replayGame();
case 'a':
//do something different for a if game hasnt started.
if(!gameStarted){
if(!gametypeChosen){ break; }//do nothing if game type hasnt been chosen
std::cout << "a pressed\n";
choice = kChoiceA;
possiblyInitGame(5, gameType); //setting number of questions. only init game if game hasnt started
break;
}
else{
if(!gametypeChosen){ break; }//do nothing if game type hasnt been chosen
std::cout << "a pressed\n";
choice = kChoiceA;
//figure out if question answered right
if(tries > 0){ break; }//dont allow multiple tries at same question
setCorrectQuestion(questionIndex, choice);
tries += 1;
break;
}
break;
case 'b':
if(!gameStarted){
if(!gametypeChosen){ break; }//do nothing if game type hasnt been chosen
std::cout << "b pressed\n";
choice = kChoiceB;
possiblyInitGame(10, gameType);
break;
}
else{
if(!gametypeChosen){ break; }//do nothing if game type hasnt been chosen
std::cout << "b pressed\n";
choice = kChoiceB;
//figure out if question answered right
if(tries > 0){ break; }//dont allow multiple tries at same question
setCorrectQuestion(questionIndex, choice);
tries += 1;
break;
}
break;
case 'c':
if(!gametypeChosen){ break; }
choice = kChoiceC;
std::cout << "c pressed\n";
if(tries > 0){ break; }
setCorrectQuestion(questionIndex, choice);
tries += 1;
break;
case 'n':
if(!gameStarted){ break; }//do nothing if game hasnt started
if(!gametypeChosen){ break; }
tries = 0; //reset tries when go to next question
choice = kNextQuestion;
if(questionIndex >= (numQuestions - 1))
{
pctCorrect = game->getPercentCorrect();
gameOver = true;
gameStarted = false;
std::cout << "ending game\n";
std::cout << gameOver;
break;
}
questionIndex += 1;
problem = game->getQuestionForProblem(questionIndex);
solution = game->getSolutionForProblem(questionIndex);
questionData = problem + solution;
break;
case 'q':
freeResources();
exit(0);
}
glutSwapBuffers();
}
void display() {
// Set every pixel in the frame buffer to the current clear color.
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
glColor3f(0, 0, 0);
//ask for number of questions before game starts
if(!gameStarted && gametypeChosen)
{
drawWord(GLUT_BITMAP_HELVETICA_18, "Type a for 5 questions or b for 10 questions", 20, 30);
}
//show game type options when game hasnt been started
if(!gameStarted && !gametypeChosen)
{
drawWord(GLUT_BITMAP_HELVETICA_18, "Type 1 for Multiplication", 20, 30);
drawWord(GLUT_BITMAP_HELVETICA_18, "Type 2 for Division", 20, 60);
drawWord(GLUT_BITMAP_HELVETICA_18, "Type 3 for Addition", 20, 90);
drawWord(GLUT_BITMAP_HELVETICA_18, "Type 4 for Subtraction", 20, 120);
}
if(!gameOver && gametypeChosen && gameStarted){
drawWord(GLUT_BITMAP_HELVETICA_18, "Type n to go to next question", 20, 20);
}
if(gameStarted && (choice == kChoiceA) && (tries > 0))
{
drawWord(GLUT_BITMAP_HELVETICA_18, "(a) picked", 80, 220);
displayAnswerResult();
}
else if(gameStarted && (choice == kChoiceB) && (tries > 0))
{
drawWord(GLUT_BITMAP_HELVETICA_18, "(b) picked", 80, 220);
displayAnswerResult();
}
else if(gameStarted && (choice == kChoiceC) && (tries > 0))
{
drawWord(GLUT_BITMAP_HELVETICA_18, "(c) picked", 80, 220);
displayAnswerResult();
}
if(gameStarted)
{
drawWord(GLUT_BITMAP_HELVETICA_18, "type a or b or c to answer question", 80, 250);
}
//if(gameStarted && (choice == kNextQuestion))
if(gameStarted)
{
displayQuestionData();
}
if(!gameOver)
{
const char * questionMessage = ("question index " + std::to_string(questionIndex)).c_str();//questionIndex);
drawWord(GLUT_BITMAP_HELVETICA_18, questionMessage, 80, 300);
}
if(gameOver)
{
//clear display
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
glColor3f(0, 0, 0);
const char * charPctCorrect = std::to_string(pctCorrect).c_str();
drawWord(GLUT_BITMAP_HELVETICA_18, "Percent Correct", 80, 80);
drawWord(GLUT_BITMAP_HELVETICA_18, charPctCorrect, 80, 100);
drawWord(GLUT_BITMAP_HELVETICA_18, "Game Over. type q to quit.", 80, 150);
drawWord(GLUT_BITMAP_HELVETICA_18, "or type p to play again", 80, 200);
}
glutSwapBuffers();
}
//This function is called on windows resize
void reshape(int x, int y)
{
glViewport(0, 0, x, y);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, x, y, 0, 0, 1);
glMatrixMode(GL_MODELVIEW);
}
// Initializes GLUT, the display mode, and main window; registers callbacks;
// enters the main event loop.
int main(int argc, char** argv) {
// Use a double buffered window in RGB mode
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE);
// Position window and give it a title.
glutInitWindowPosition(100, 100);
glutInitWindowSize(400, 450);
glutCreateWindow("naArithmetic Game");
glutReshapeFunc(reshape);
// Tell GLUT that whenever the main window needs to be repainted that it
// should call the function display().
glutDisplayFunc(display);
glutKeyboardFunc(keyPress); //for keyboard input
glutIdleFunc(display);
// Tell GLUT to start reading and processing events. This function
// never returns; the program only exits when the user closes the main
// window or kills the process.
glutMainLoop();
}
//work in progress
//-accepting user input to check if answer was correct, accepting game type input
//-added header, beginning display of question
/*inspired by
https://cs.lmu.edu/~ray/notes/openglexamples/
https://stackoverflow.com/questions/12183008/how-to-use-enums-in-c
//https://gist.github.com/alyakhtar/0c9b4dbeb8281983cf0d
g++ -framework OpenGL -framework GLUT -o out main.cpp core.cpp -Wno-deprecated-declarations
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment