Created
June 17, 2022 00:29
-
-
Save andrewljohnson/67ca96e619a26b14a69e1f937aeb5d9d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <string> | |
#include <vector> | |
using namespace std; | |
// Current Cards | |
// Mountain | |
// Future Cards | |
// Forest | |
// Lightning Bolt, Giant Growth, Earthquake | |
// Kird Ape, Granite Gargoyle, Llanowar Elf | |
enum card_name { | |
Mountain | |
}; | |
// https://stackoverflow.com/questions/3342726/c-print-out-enum-value-as-text | |
std::ostream& operator<<(std::ostream& out, const card_name value){ | |
const char* s = 0; | |
#define PROCESS_VAL(p) case(p): s = #p; break; | |
switch(value){ | |
PROCESS_VAL(Mountain); | |
} | |
#undef PROCESS_VAL | |
return out << s; | |
} | |
enum card_type { | |
Land, | |
}; | |
ostream& operator<<(ostream& out, const card_type value){ | |
const char* s = 0; | |
#define PROCESS_VAL(p) case(p): s = #p; break; | |
switch(value){ | |
PROCESS_VAL(Land); | |
} | |
#undef PROCESS_VAL | |
return out << s; | |
} | |
enum effect_name { | |
mana_red | |
}; | |
enum effect_trigger { | |
count_mana | |
}; | |
enum target_type { | |
self | |
}; | |
enum game_step { | |
draw_step, | |
main_first, | |
end_step | |
}; | |
enum move_type { | |
pass, | |
select_card | |
}; | |
ostream& operator<<(ostream& out, const move_type value){ | |
const char* s = 0; | |
#define PROCESS_VAL(p) case(p): s = #p; break; | |
switch(value){ | |
PROCESS_VAL(pass); | |
PROCESS_VAL(select_card); | |
} | |
#undef PROCESS_VAL | |
return out << s; | |
} | |
// effects get mapped to EffectDef functions when cards are instantiated for a deck | |
struct Effect { | |
effect_name name; | |
int amount; | |
target_type targetType; | |
effect_trigger trigger; | |
}; | |
// a struct, based on player actions, that gets passed into an EffectDef function | |
struct TargetInfo { | |
int amount; | |
target_type targetType; | |
int targetId; | |
}; | |
// map all of a Card's Effects into functions of this signature when cards are instantiated for a deck | |
typedef void (*EffectDef) (int effectOwner, Effect e, TargetInfo targetInfo); | |
struct Card { | |
int id; // assign when card is instantiated for a deck | |
card_name name; | |
card_type cardType; | |
bool tapped = false; | |
vector<Effect> effects; | |
vector<EffectDef> activatedEffectDefs; | |
}; | |
void doManaEffect(int effectOwner, Effect e, TargetInfo t) { | |
// add mana to a player's pool | |
// do Effect e for Card c based on TargetInfo t | |
} | |
Card mountain() { | |
// make a card with one effect | |
Card m; | |
m.name = Mountain; | |
m.cardType = Land; | |
Effect mManaEffect; | |
mManaEffect.name = mana_red; | |
mManaEffect.amount = 1; | |
mManaEffect.targetType = self; | |
mManaEffect.trigger = count_mana; | |
m.effects.push_back(mManaEffect); | |
m.activatedEffectDefs.push_back(doManaEffect); | |
return m; | |
} | |
struct Move { | |
move_type moveType; | |
int playerId; | |
int cardId; | |
}; | |
class Player { | |
vector< Card > hand; | |
vector< Card > library; | |
vector< Card > inPlay; | |
int id_; | |
string username_; | |
int life = 20; | |
bool drewFromEmptyLibrary = false; | |
int landsPlayableThisTurn = 1; | |
int landsPlayedThisTurn = 0; | |
public: | |
int id() const { return id_; } | |
string username() const { return username_; } | |
Player(int playerId, string playerUsername) { | |
id_ = playerId; | |
username_ = playerUsername; | |
} | |
void addCardToLibrary (Card c) { | |
library.push_back(c); | |
} | |
void drawCard () { | |
if (library.size() == 0) { | |
drewFromEmptyLibrary = true; | |
return; | |
} | |
hand.push_back(library.back()); | |
library.pop_back(); | |
// cout << username_ << " drew a card.\n"; | |
} | |
int librarySize() { | |
return library.size(); | |
} | |
int handSize() { | |
return hand.size(); | |
} | |
int inPlaySize() { | |
return inPlay.size(); | |
} | |
int getLife() { | |
return life; | |
} | |
int didDrawFromEmptyLibrary() { | |
return drewFromEmptyLibrary; | |
} | |
vector< Move > validMoves() { | |
vector< Move > moves; | |
// playable land moves | |
for (int x=0;x<hand.size();x++) { | |
Card& card = hand[x]; | |
if (card.cardType == Land && landsPlayedThisTurn < landsPlayableThisTurn) { | |
moves.push_back((struct Move){.moveType = select_card, .cardId = card.id, .playerId = id_}); | |
} | |
} | |
moves.push_back((struct Move){.moveType = pass, .playerId = id_}); | |
return moves; | |
} | |
void playMove(Move move) { | |
if (move.moveType == select_card) { | |
int cardIndex = 0; | |
for(int x=0;x<hand.size();x++) { | |
if (hand[x].id == move.cardId) { | |
cardIndex = x; | |
break; | |
} | |
} | |
Card c = hand[cardIndex]; | |
inPlay.push_back(c); | |
hand.erase(hand.begin() + cardIndex); | |
if (c.cardType == Land) { | |
landsPlayedThisTurn++; | |
} | |
return; | |
} | |
} | |
void resetLandsPlayedThisTurn() { | |
landsPlayedThisTurn = 0; | |
} | |
}; | |
class Game { | |
// the index of the player that currently has priority | |
int indexOfPlayerWithPriority = 0; | |
// each new card created in a game has a sequential ID | |
int lastCardId = 0; | |
// the max number of cards a player can have | |
int maxHandSize = 7; | |
game_step gameStep = main_first; | |
int turn = 0; | |
public: | |
// a vector of players in the game | |
vector< Player > players; | |
void addPlayer (Player p) { | |
players.push_back(p); | |
} | |
void addCardToLibraryForPlayerAtIndex (Card c, int index) { | |
Player& p = players[index]; | |
c.id = lastCardId; | |
lastCardId++; | |
p.addCardToLibrary(c); | |
// cout << "Added a " << c.name << " (" << c.cardType << ") to " << p.username() << "'s library.\n"; | |
} | |
void drawCardForPlayerAtIndex (int index) { | |
Player& p = players[index]; | |
p.drawCard(); | |
} | |
void makeDecks() { | |
int deckSize = 60; | |
for (int x=0; x<deckSize; x++) { | |
addCardToLibraryForPlayerAtIndex(mountain(), 0); | |
addCardToLibraryForPlayerAtIndex(mountain(), 1); | |
} | |
} | |
void drawOpeningHands() { | |
for (int x=0; x<maxHandSize; x++) { | |
drawCardForPlayerAtIndex(0); | |
drawCardForPlayerAtIndex(1); | |
} | |
gameStep = main_first; | |
} | |
void printGameStatus() { | |
for (int x=0; x < players.size(); x++) { | |
cout << players[x].username() << " has " << players[x].librarySize() << " cards in library"; | |
cout << ", " << players[x].inPlaySize() << " cards in play"; | |
cout << " and " << players[x].handSize() << " cards in hand.\n"; | |
} | |
cout << "Valid moves are: "; | |
for (Move move: validMoves()) | |
cout << "(" << move.moveType << ", " << move.cardId << ", " << move.playerId << "), "; | |
cout << "\n"; | |
} | |
vector< Move > validMoves() { | |
if (turnPlayer().id() != currentPlayer().id()) { | |
vector< Move > moves; | |
moves.push_back((struct Move){.moveType = pass, .playerId = currentPlayer().id()}); | |
return moves; | |
} | |
Player p = players[indexOfPlayerWithPriority]; | |
return p.validMoves(); | |
} | |
Player& currentPlayer() { | |
return players[indexOfPlayerWithPriority]; | |
} | |
Player& turnPlayer() { | |
if( turn % 2 == 0) return players[0]; | |
return players[1]; | |
} | |
void passPriority() { | |
if (indexOfPlayerWithPriority == 0) { | |
indexOfPlayerWithPriority = 1; | |
} else indexOfPlayerWithPriority = 0; | |
} | |
void playMove(Move move) { | |
if (move.moveType == pass) { | |
passPriority(); | |
if (gameStep == main_first) { | |
if(turnPlayer().id() == currentPlayer().id()) { | |
gameStep = end_step; | |
} | |
} | |
if (gameStep == end_step) { | |
if(turnPlayer().id() == currentPlayer().id()) { | |
passPriority(); | |
gameStep = draw_step; | |
turnPlayer().resetLandsPlayedThisTurn(); | |
turn += 1; | |
currentPlayer().drawCard(); | |
if (currentPlayer().didDrawFromEmptyLibrary()) { | |
return; | |
} | |
gameStep = main_first; | |
} | |
} | |
return; | |
} | |
currentPlayer().playMove(move); | |
} | |
void playRandomMove() { | |
vector< Move > moves = validMoves(); | |
// use arc4random() instead if we want to change the seed each run on Mac | |
// playMove(moves[rand() % moves. size()]); | |
playMove(moves[0]); | |
} | |
bool isOver() { | |
if (players[0].getLife() <= 0) return true; | |
if (players[1].getLife() <= 0) return true; | |
if (players[0].didDrawFromEmptyLibrary()) return true; | |
if (players[1].didDrawFromEmptyLibrary()) return true; | |
return false; | |
} | |
}; | |
int main() { | |
Game game; | |
game.addPlayer(Player(0, "Spike")); | |
game.addPlayer(Player(1, "Lee")); | |
game.makeDecks(); | |
game.drawOpeningHands(); | |
game.printGameStatus(); | |
int movesToPlay = 9; | |
int i = 0; | |
while (!game.isOver()) { | |
game.playRandomMove(); | |
if (game.isOver()) { | |
cout << "GAME OVER"; | |
} else { | |
game.printGameStatus(); | |
} | |
// i++; | |
// if (i >= movesToPlay) { | |
// break; | |
// } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment