Overhaul makefile, pass random variables by reference to avoid recreating

This commit is contained in:
Tyler Beckman 2024-09-19 22:06:16 -06:00
parent 010a9188a6
commit 6e83262859
Signed by: Ty
GPG key ID: 2813440C772555A4
4 changed files with 226 additions and 175 deletions

View file

@ -1,24 +1,69 @@
TARGET = A1 TARGET = A1
SRC_FILES = main.cpp hands.cpp SRC_FILES = main.cpp hands.cpp
# NO EDITS BELOW THIS LINE # Tyler's custom makefile extensions
CXX = g++ -g .DEFAULT_GOAL := all # Necessary so `make` doesn't run the "pack" target, as it is declared before "all"
.PHONY: pack clean-run c run
## Adds only the necessary files for build into a .tar.gz file, named appropriately
ARCHIVED_FILES = Makefile $(SRC_FILES) $(SRC_FILES:.cpp=.h) $(SRC_FILES:.cpp=.hpp)
pack:
tar --ignore-failed-read -czvf $(TARGET).tar.gz $(shell echo $(ARCHIVED_FILES) | xargs ls -d 2>/dev/null)
## Runs the pack target and then attempts to build & run the program to make sure it functions correctly
pack-test: pack
$(eval TMP := $(shell mktemp -d))
tar -xvzf $(TARGET).tar.gz --directory $(TMP)
make -C $(TMP)
$(TMP)/$(TARGET)
rm -rf $(TMP)
## An extension of the clean command that is shorter to type and removes a potential .tar.gz file
c: clean
$(DEL) -f $(TARGET).tar.gz
## Simply builds and then executes the program
run: all
./$(TARGET)
# NO EDITS NEEDED BELOW THIS LINE
CXX = g++
CXXFLAGS = -O2
CXXFLAGS_DEBUG = -g
CXXFLAGS_WARN = -Wall -Wextra -Wunreachable-code -Wshadow -Wpedantic
CPPVERSION = -std=c++17
OBJECTS = $(SRC_FILES:.cpp=.o) OBJECTS = $(SRC_FILES:.cpp=.o)
ifeq ($(shell echo "Windows"), "Windows") ifeq ($(shell echo "Windows"), "Windows")
TARGET := $(TARGET).exe TARGET := $(TARGET).exe
DEL = del DEL = del
Q =
else else
DEL = rm -f DEL = rm -f
Q = "
endif endif
all: $(TARGET) all: $(TARGET)
$(TARGET): $(OBJECTS) $(TARGET): $(OBJECTS)
$(CXX) -std=c++17 -o $@ $^ $(CXX) -o $@ $^
%.o: %.cpp .cpp.o:
$(CXX) -std=c++17 -o $@ -c $< $(CXX) $(CXXFLAGS) $(CPPVERSION) $(CXXFLAGS_DEBUG) $(CXXFLAGS_WARN) -o $@ -c $<
clean: clean:
$(DEL) $(TARGET) $(OBJECTS) $(DEL) -f $(TARGET) $(OBJECTS) Makefile.bak
depend:
@sed -i.bak '/^# DEPENDENCIES/,$$d' Makefile
@$(DEL) sed*
@echo $(Q)# DEPENDENCIES$(Q) >> Makefile
@$(CXX) -MM $(SRC_FILES) >> Makefile
.PHONY: all clean depend
# DEPENDENCIES
main.o: main.cpp hands.h
hands.o: hands.cpp hands.h

163
hands.cpp
View file

@ -1,6 +1,5 @@
#include "hands.h" #include "hands.h"
#include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <random> #include <random>
@ -8,110 +7,118 @@
#include <cstdlib> #include <cstdlib>
std::unique_ptr<Hand> Hand::fromChar(const char letter) { std::unique_ptr<Hand> Hand::fromChar(const char letter) {
switch (letter) { switch (letter) {
case 'R': case 'R':
case 'r': case 'r':
return std::make_unique<Rock>(); return std::make_unique<Rock>();
case 'P': case 'P':
case 'p': case 'p':
return std::make_unique<Paper>(); return std::make_unique<Paper>();
case 'S': case 'S':
case 's': case 's':
return std::make_unique<Scissors>(); return std::make_unique<Scissors>();
case 'L': case 'L':
case 'l': case 'l':
return std::make_unique<Lizard>(); return std::make_unique<Lizard>();
case 'O': case 'O':
case 'o': case 'o':
return std::make_unique<Spock>(); return std::make_unique<Spock>();
default: default:
std::cout << "Invalid choice" << std::endl; std::cout << "Invalid choice" << std::endl;
std::exit(1); std::exit(1);
} }
} }
std::unique_ptr<Hand> Hand::generateRandom() { std::unique_ptr<Hand>
std::mt19937 mt( Hand::generateRandom(std::mt19937 &mt,
std::chrono::steady_clock::now().time_since_epoch().count()); std::uniform_int_distribution<int> &intDist) {
std::uniform_int_distribution<int> intDist(0, 4); switch (intDist(mt)) {
case 0:
switch (intDist(mt)) { return std::make_unique<Rock>();
case 0: case 1:
return std::make_unique<Rock>(); return std::make_unique<Paper>();
case 1: case 2:
return std::make_unique<Paper>(); return std::make_unique<Scissors>();
case 2: case 3:
return std::make_unique<Scissors>(); return std::make_unique<Lizard>();
case 3: case 4:
return std::make_unique<Lizard>(); return std::make_unique<Spock>();
case 4: default:
return std::make_unique<Spock>(); std::cerr << "An invalid random number was generated, this should "
default: "be impossible"
std::cerr << "An invalid random number was generated, this should " << std::endl;
"be impossible" std::exit(1);
<< std::endl; }
std::exit(1);
}
} }
// List of function implementations for all of the different types of Hand // List of function implementations for all of the different types of Hand
std::string Rock::getHandName() const { return "rock"; } std::string Rock::getHandName() const { return "rock"; }
GameResult Rock::compareAgainst(const Hand &other) { GameResult Rock::compareAgainst(const Hand &other) {
const std::string otherName = other.getHandName(); const std::string otherName = other.getHandName();
if (otherName == "rock") if (otherName == "rock") {
return GameResult::Tie; return GameResult::Tie;
if (otherName == "scissors" || otherName == "lizard") }
return GameResult::Win; if (otherName == "scissors" || otherName == "lizard") {
else return GameResult::Win;
return GameResult::Loss; } else {
return GameResult::Loss;
}
} }
std::string Paper::getHandName() const { return "paper"; } std::string Paper::getHandName() const { return "paper"; }
GameResult Paper::compareAgainst(const Hand &other) { GameResult Paper::compareAgainst(const Hand &other) {
const std::string otherName = other.getHandName(); const std::string otherName = other.getHandName();
if (otherName == "paper") if (otherName == "paper") {
return GameResult::Tie; return GameResult::Tie;
if (otherName == "rock" || otherName == "spock") }
return GameResult::Win; if (otherName == "rock" || otherName == "spock") {
else return GameResult::Win;
return GameResult::Loss; } else {
return GameResult::Loss;
}
} }
std::string Scissors::getHandName() const { return "scissors"; } std::string Scissors::getHandName() const { return "scissors"; }
GameResult Scissors::compareAgainst(const Hand &other) { GameResult Scissors::compareAgainst(const Hand &other) {
const std::string otherName = other.getHandName(); const std::string otherName = other.getHandName();
if (otherName == "scissors") if (otherName == "scissors") {
return GameResult::Tie; return GameResult::Tie;
if (otherName == "paper" || otherName == "lizard") }
return GameResult::Win; if (otherName == "paper" || otherName == "lizard") {
else return GameResult::Win;
return GameResult::Loss; } else {
return GameResult::Loss;
}
} }
std::string Lizard::getHandName() const { return "lizard"; } std::string Lizard::getHandName() const { return "lizard"; }
GameResult Lizard::compareAgainst(const Hand &other) { GameResult Lizard::compareAgainst(const Hand &other) {
const std::string otherName = other.getHandName(); const std::string otherName = other.getHandName();
if (otherName == "lizard") if (otherName == "lizard") {
return GameResult::Tie; return GameResult::Tie;
if (otherName == "spock" || otherName == "paper") }
return GameResult::Win; if (otherName == "spock" || otherName == "paper") {
else return GameResult::Win;
return GameResult::Loss; } else {
return GameResult::Loss;
}
} }
std::string Spock::getHandName() const { return "spock"; } std::string Spock::getHandName() const { return "spock"; }
GameResult Spock::compareAgainst(const Hand &other) { GameResult Spock::compareAgainst(const Hand &other) {
const std::string otherName = other.getHandName(); const std::string otherName = other.getHandName();
if (otherName == "spock") if (otherName == "spock") {
return GameResult::Tie; return GameResult::Tie;
if (otherName == "scissors" || otherName == "rock") }
return GameResult::Win; if (otherName == "scissors" || otherName == "rock") {
else return GameResult::Win;
return GameResult::Loss; } else {
return GameResult::Loss;
}
} }

26
hands.h
View file

@ -1,5 +1,8 @@
#ifndef HANDS_H
#define HANDS_H
#include <istream> #include <istream>
#include <memory> #include <memory>
#include <random>
enum GameResult { Win, Tie, Loss }; enum GameResult { Win, Tie, Loss };
@ -7,8 +10,9 @@ class Hand {
public: public:
// Converts a user-inputted character to the correct instance of Hand. // Converts a user-inputted character to the correct instance of Hand.
static std::unique_ptr<Hand> fromChar(char); static std::unique_ptr<Hand> fromChar(char);
// Randomly returns one of the variants of Hand. // Randomly returns one of the variants of Hand, as generated by a Mersenne
static std::unique_ptr<Hand> generateRandom(); // Twister function seeded by the current unix time
static std::unique_ptr<Hand> generateRandom(std::mt19937&, std::uniform_int_distribution<int>&);
// Returns the lowercase name of this type of hand, for example "rock" or // Returns the lowercase name of this type of hand, for example "rock" or
// "scissors". // "scissors".
@ -17,35 +21,39 @@ class Hand {
// returned value is from the perspective of this instance of hand. For // returned value is from the perspective of this instance of hand. For
// example, if GameResult::Win is returned, the instance of Hand wins // example, if GameResult::Win is returned, the instance of Hand wins
// against the passed argument Hand. // against the passed argument Hand.
virtual GameResult compareAgainst(const Hand&) = 0; virtual GameResult compareAgainst(const Hand &) = 0;
}; };
// List of all types of RPSLS hands that can be played
class Rock : public Hand { class Rock : public Hand {
public: public:
std::string getHandName() const override; std::string getHandName() const override;
GameResult compareAgainst(const Hand&) override; GameResult compareAgainst(const Hand &) override;
}; };
class Paper : public Hand { class Paper : public Hand {
public: public:
std::string getHandName() const override; std::string getHandName() const override;
GameResult compareAgainst(const Hand&) override; GameResult compareAgainst(const Hand &) override;
}; };
class Scissors : public Hand { class Scissors : public Hand {
public: public:
std::string getHandName() const override; std::string getHandName() const override;
GameResult compareAgainst(const Hand&) override; GameResult compareAgainst(const Hand &) override;
}; };
class Lizard : public Hand { class Lizard : public Hand {
public: public:
std::string getHandName() const override; std::string getHandName() const override;
GameResult compareAgainst(const Hand&) override; GameResult compareAgainst(const Hand &) override;
}; };
class Spock : public Hand { class Spock : public Hand {
public: public:
std::string getHandName() const override; std::string getHandName() const override;
GameResult compareAgainst(const Hand&) override; GameResult compareAgainst(const Hand &) override;
}; };
#endif // HANDS_H

155
main.cpp
View file

@ -1,6 +1,13 @@
/* CSCI 200: Assignment 1 (Rock Paper Scissors): Tyler Beckman /* CSCI 200: Assignment 1 (Rock Paper Scissors): Tyler Beckman
* *
* Author: Tyler Beckman * Author: Tyler Beckman
* Resources used:
* While learning how to use classes & polymorphism in the way I wanted to,
* student Maxwell Gross helped me debug why the way I was trying to use classes
* didn't work (virtual fields not existing, where I needed to put class
* definitions vs method definitions, etc). No code was copied or written for
* me, he just helped me understand how all of these structures I was used to in
* other langs like Java and JS worked in cpp :)
* *
* Description: * Description:
* A C++ program to play rock paper scissors (lizard spock) against * A C++ program to play rock paper scissors (lizard spock) against
@ -15,99 +22,83 @@
* multiple other languages, so I felt it would be fun to use this as an excuse * multiple other languages, so I felt it would be fun to use this as an excuse
* to learn how the concepts work in cpp. I apologize if this is more * to learn how the concepts work in cpp. I apologize if this is more
* complicated than we were supposed to make it, but it was fun and it should * complicated than we were supposed to make it, but it was fun and it should
* still be a perfectly functional and point-scoring program :) * still be a perfectly functional and point-scoring program :). To clarify, I
* * didn't copy any code but I did use multiple online tutorials and references
* Academic integrity: * to learn how classes and the such work in cpp.
* 1. Because I did go overboard on making an object-oriented program when we
* haven't even learned how classes yet, I did use a lot of online cpp syntax
* reference and explainers to learn how classes and inheritance work in cpp.
* This includes mainly cppreference.com, but also geeksforgeeks.org and
* tutorialspoint to figure out the basics. Nowhere did I actually copy the
* exact structures, I simply used it to figure out how to do what I wanted and
* what I knew how to do in other languages such as Java and JavaScript (have a
* "which one wins" function on each sibling class of "Hand"). I am not entirely
* sure if this needs cited, but given we have learned none of this yet, I just
* wanted to clarify that this is all my code - not copied from the internet,
* ChatGPT, Copilot, or anything else other than my code.
* 2. Also since I was making a program with structures that I hadn't quite
* learned in this language yet, I did ask Max[TODO FILL IN NAME] (someone with
* experience in cpp) for debugging assistance when concepts I was used to in
* other languages didn't work well in cpp. For example, I wasn't used to class
* definitions and function bodies being separate, so he helped me figure out
* the correct structure there so I could properly reference sibling classes in
* methods on the parent class.
*/ */
#include "hands.h" #include "hands.h"
#include <chrono>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <ostream> #include <ostream>
int main(void) { int main(void) {
int wins = 0; int wins = 0;
int losses = 0; int losses = 0;
int ties = 0; int ties = 0;
while (true) { std::mt19937 mt(std::chrono::steady_clock::now().time_since_epoch().count());
// Take a letter input and convert it to the correct class type for std::uniform_int_distribution<int> intDist(0, 4);
// later use
char handLetter;
std::cout << "Welcome to a round of [R]ock [P]aper [S]cissors [L]izard "
"Sp[O]ck! Please enter "
"a hand to play (R/P/S/L/O): ";
std::cin >> handLetter;
const std::unique_ptr<Hand> userHand = Hand::fromChar(handLetter);
// Generate a random "computer" hand and display the two choices while (true) {
const std::unique_ptr<Hand> computerHand = Hand::generateRandom(); // Take a letter input and convert it to the correct class type for
// later use
char handLetter;
std::cout << "Welcome to a round of [R]ock [P]aper [S]cissors [L]izard "
"Sp[O]ck! Please enter "
"a hand to play (R/P/S/L/O): ";
std::cin >> handLetter;
const std::unique_ptr<Hand> userHand = Hand::fromChar(handLetter);
std::cout << std::endl // Generate a random "computer" hand and display the two choices
<< "Player chose " << userHand->getHandName() << std::endl const std::unique_ptr<Hand> computerHand =
<< "Computer chose " << computerHand->getHandName() Hand::generateRandom(mt, intDist);
<< std::endl
<< std::endl;
// Compare the two hands, add to statistics, and give the correct output std::cout << std::endl
// and explanation << "Player chose " << userHand->getHandName() << std::endl
switch (userHand->compareAgainst(*computerHand)) { << "Computer chose " << computerHand->getHandName() << std::endl
case Win: << std::endl;
std::cout << "The player wins, as " << userHand->getHandName()
<< " beats " << computerHand->getHandName() << "!"
<< std::endl;
wins++;
break;
case Tie:
std::cout << "No one wins, as " << userHand->getHandName()
<< " is the same as " << computerHand->getHandName()
<< "." << std::endl;
ties++;
break;
case Loss:
std::cout << "The computer wins, as "
<< computerHand->getHandName() << " beats "
<< userHand->getHandName() << "." << std::endl;
losses++;
break;
}
// Ask if the user would like to play again or exit // Compare the two hands, add to statistics, and give the correct output
char playAgain; // and explanation
std::cout << "Do you want to play again (Y/N)? "; switch (userHand->compareAgainst(*computerHand)) {
std::cin >> playAgain; case Win:
switch (playAgain) { std::cout << "The player wins, as " << userHand->getHandName()
case 'Y': << " beats " << computerHand->getHandName() << "!"
case 'y': << std::endl;
std::cout << std::endl; wins++;
continue; break;
case 'N': case Tie:
case 'n': std::cout << "No one wins, as " << userHand->getHandName()
default: << " is the same as " << computerHand->getHandName() << "."
std::cout << std::endl << std::endl;
<< "Thanks for playing!" << std::endl ties++;
<< "You won " << wins << " game(s), lost " << losses break;
<< " game(s), and tied " << ties << " time(s)." case Loss:
<< std::endl; std::cout << "The computer wins, as " << computerHand->getHandName()
std::exit(0); << " beats " << userHand->getHandName() << "." << std::endl;
} losses++;
} break;
}
// Ask if the user would like to play again or exit
char playAgain;
std::cout << "Do you want to play again (Y/N)? ";
std::cin >> playAgain;
switch (playAgain) {
case 'Y':
case 'y':
std::cout << std::endl;
continue;
case 'N':
case 'n':
default:
std::cout << std::endl
<< "Thanks for playing!" << std::endl
<< "You won " << wins << " game(s), lost " << losses
<< " game(s), and tied " << ties << " time(s)." << std::endl;
std::exit(0);
}
}
} }