Skip to content

Instantly share code, notes, and snippets.

@andrewljohnson
Created June 17, 2022 00:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewljohnson/67ca96e619a26b14a69e1f937aeb5d9d to your computer and use it in GitHub Desktop.
Save andrewljohnson/67ca96e619a26b14a69e1f937aeb5d9d to your computer and use it in GitHub Desktop.
#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