ETHAN CROOKS

Programming Magic The Gathering - Part 1: Intro

2020-10-23

My second year of university is now underway, and I have decided it a good idea to start a devlog of sorts. So here it is! Unfortunately for you, what I have chosen for my gameplay programming project is perhaps the nerdiest thing imaginable.

What is MTG?

A screenshot of MTG Arena, taken from the official website

Magic: The Gathering is a card game created by Richard Garfield and first released in 1993. Players cast spells, attack with creatures and more in an effort to reduce their opponent's life total from 20 to 0. As it has been around for decades, there are over 20,000 unique cards printed, with each new set adding more mechanics to an increasingly complex game.

Serra angel
Cultivate
Evolving Wilds

MTG is (arguably) well designed enough to allow for players to only need a broad idea of the game's rules to play. While playing a game you will occasionally have to refer to the exhaustive official comprehensive rules to resolve corner cases. However, if you are using a computerised version like MTG: Online or MTG: Arena, the computer worries about all that for you! Therefore, with my implementation I have no option other than to make sure that every part of the hefty 7.5k-line text file is represented.

Just kidding. Screw that.

What am I doing?

This project is very flexible - there are many stages where I can stop implementing new mechanics and starts wrapping up into a working game. I may end up with two full standard decks going against each other (doubtful), or just with simple creatures with minimal abilities - I will be happy with either.

I've never really made a proper turn-based game before, especially one that needs to be as flexible as MTG. Every attribute of a card can be overridden in some way, so I need to make a system that allows for these kind of changes to take place.

Frogify
Destiny Spinner
Athreos, Shroud-Veiled

I've decided to use C# for this, making a library that I can import into Unity if/when I want to. I've already made a bunch of data structures to represent some MTG mechanics, such as mana costs and phases. Colour can be very nicely represented as a bitmask:

[Flags] public enum Color
{
    Generic = 0,
    White = 1,
    Blue = 2,
    Black = 4,
    Red = 8,
    Green = 16,

    // Allied colour pairs
    Azorius = White | Blue,
    Dimir = Blue | Black,
    Rakdos = Black | Red,
    Gruul = Red | Green,
    Selesnya = Green | White,

    // Enemy colour pairs
    Orzhov = White | Black,
    Izzet = Blue | Red,
    Golgari = Black | Green,
    Boros = Red | White,
    Simic = Green | Blue
}

I've also experimented with representing a modification of an attribute as a C# generic type. When modifications are being applied, you can throw the attribute into the Modify function and it should spit out the new value. This means if there are multiple modifications they can be chained together easily.

using System;
using System.Collections.Generic;

namespace MTGLib
{
    public class PowerMod : IntModification { }
    public class ToughnessMod : IntModification { }
    public class ColorMod : ColorModification { }
    // Controller uses Mod<int> not IntMod, as you do not add/subtract controllers
    public class ControllerMod : Modification<int> { }

    public class CardTypeMod : HashSetModification<MTGObject.CardType> { }
    public class SuperTypeMod : HashSetModification<MTGObject.SuperType> { }
    public class SubTypeMod : HashSetModification<MTGObject.SubType> { }

    public abstract class Modification
    {
        public enum Operation { Override, Add, Subtract }
        public Operation operation = Operation.Override;
    }

    public abstract class Modification<T> : Modification
    {
        public T value;

        public virtual T Modify(T original)
        {
            switch (operation)
            {
                case Operation.Override:
                    return Override(value);
                case Operation.Add:
                    return Add(value);
                case Operation.Subtract:
                    return Subtract(value);
                default:
                    throw new ArgumentException();
            }
        }

        protected virtual T Override(T original)
        {
            return value;
        }
        // Generic can't have an add or subtract
        protected virtual T Add (T original)
        {
            throw new NotImplementedException();
        }
        protected virtual T Subtract (T original)
        {
            throw new NotImplementedException();
        }
    }

    public abstract class IntModification : Modification<int>
    {
        protected override int Add(int original)
        {
            return original + value;
        }
        protected override int Subtract(int original)
        {
            return original - value;
        }
    }

    public abstract class ManaCostModification : Modification<ManaCost>
    {
        protected override ManaCost Add(ManaCost original)
        {
            return original + value;
        }
        protected override ManaCost Subtract(ManaCost original)
        {
            return original - value;
        }
    }

    public abstract class ListModification<T> : Modification<List<T>>
    {
        protected override List<T> Add(List<T> original)
        {
            List<T> toReturn = new List<T>(original);
            toReturn.AddRange(value);
            return toReturn;
        }
        protected override List<T> Subtract(List<T> original)
        {
            List<T> toReturn = new List<T>(original);
            foreach (T x in value)
            {
                toReturn.Remove(x);
            }
            return toReturn;
        }
    }

    public abstract class HashSetModification<T> : Modification<HashSet<T>>
    {
        protected override HashSet<T> Add(HashSet<T> original)
        {
            HashSet<T> toReturn = new HashSet<T>(original);
            foreach (T x in value)
            {
                toReturn.Add(x);
            }
            return toReturn;
        }
        protected override HashSet<T> Subtract(HashSet<T> original)
        {
            HashSet<T> toReturn = new HashSet<T>(original);
            foreach (T x in value)
            {
                toReturn.Remove(x);
            }
            return toReturn;
        }
    }

    public abstract class ColorModification : Modification<Color>
    {
        protected override Color Add(Color original)
        {
            return original | value;
        }
        protected override Color Subtract(Color original)
        {
            return original & ~value;
        }
    }
}

My first attempts at developing this looks really promising. Hopefully I manage to come out with something playable! I'll try to make extra blog posts detailing other systems in the future.

View my source code here.