bstordrup / MasterGrab

MasterGrab is a WPF game where a human player plays against several computer players (=Robots). You can program your own Robot in C#.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

MasterGrab Logo

Introduction

MasterGrab is an open source, turn-based PC game where a human player plays against several computer players (=robots), trying to grab all countries on a random map. The game is quite fun, because the random map poses different problems every time and the robots make MasterGrab a dynamic play. To win the game, strategic thinking is required.

For programmers, it might be even more fun to write your own robot, which can be done with only a few lines of code.

How you can play MasterGrab is described in the Help section of the game. This rest of this document provides the information needed to write a robot.

Overview MasterGrab VS Solution

The VS Solution contains the following projects:

  • MasterGrab: GUI, contains all WPF and Windows related code
  • BL: Business Logic, i.e. the game engine and all data
  • BLTest: Unit tests of BL
  • Robots: Here you can add your own class inheriting from Robot

The projects use .NET 7 and has no other dependencies. Just get MasterGrab from Github and you are ready to run.

Writing your own Robot

Basically, you have just to write a new class inheriting from Robot and place it into the Robots project. When MasterGrab starts, it searches for these classes and displays them in the Options window, where the user can select them for the next game.

Classes involved in a Game

The game is managed by the GameController. The user can select in the Options Window which robot types and how many of them should play, how many countries should be on the map and other settings. When the user presses New Game, the options get sent to the GameController, which starts a new Game. It waits first for the user to make a move, then it asks each Robot to make a move (DoPlanMove()). The robot gets a copy of the latest game data, like which Country is owned by which Player (human or robot) and what is the size of the army in that Country. The Robot cannot change any of this data, that would open the door to cheating. The Robot can only tell the GameController which is the next move it would like to make.

Classes Diagram

Some data changes during a game, like who owns which country and other data does not change during one game, like which countries are neighbours (Options, GameFix, CountryFix).

Options contains all the information needed to create a new game,

Whenever a robot can make a move, it gets the Robot instance which represents him, holding all the information a robot needs, like RobotCountryIds which is a collection of all the country (actually the countries' IDs) he owns.

Player has a Game property, which is actually the root of the class tree. Map is a special collection holding all the countries.

Game properties

  • Map is a collection of countries
  • Players allows the robot to investigate the data of all other players
  • Results shows the move of every player in the last round and if it was a success or failure.
  • AttackFactor is important for a robot to decide if he can attack an enemy country. He first adds up the army sizes of his own countries neighbouring that enemy country. That total gets multiplied by AttackFactor, which is smaller than 1. If the result is greater than the defending army's size, the attack wins.

Player properties

The robot's Player instance also holds CountryIds with a list of all countries that player (robot) holds. Note that these are only unique country ID numbers, which can be used to get the country data like this:

var country = Map[countryId];

Country properties

Some Country properties are not interesting for robots, like its coordinates on the screen. Country properties used by robots to calculate their next move:

  • NeighbourIds list the neighbour countries' IDs.
  • ArmySize number of attackers in own countries or defenders in enemy countries.

Code of a very simple Robot

One can use SimpleRobot.cs as starting point for your own robot:

using System.Collections.Generic;

namespace MasterGrab {

  /// <summary>
  /// Simple Robot player using only 1 country to attack
  /// </summary>
  [Robot(name: "SimpleRobot", isUsedForDefault: false)]
  public class SimpleRobot: Robot {

Any namespace can be used. Important is that SimpleRobot inherits from Robot. Using the RobotAttribute is optional. It allows changing the class name to a nicer name seen by the user and to indicate if that robot should be used as default option.

Robot provides SimpleRobot easy access to Player (the robot itself), Game, Map, RobotCountryIds (owned countries) and Results.

    readonly List<Country> attackers;

Note that a robot gets for each move completely new instances of Game, countries, etc. For example, it is not possible to reuse a country instance from a previous move. However, Country.ID and Player.ID are constant. So if a robot wants to store information about another player over several moves, he must combine that player's ID with the rest of the data he wants to store.

The variable attackers is not used to store information between 2 moves, but for efficiency reasons it gets created only once and then constantly reused.

    public SimpleRobot(): base() {
      attackers = new List<Country>();
    }

GameController calls DoPlanMove() when it is time for the robot to make the next move. Here is where you place the code for your robot.

    protected override Move DoPlanMove() {
      Country? attacker = null;
      Country? target = null;
      double ratio = 0;

Strategy used by SimpleRobot:

  • loop through all countries this robot owns
  • find the best neighbour to attack. SimpleRobot tries to optimise:
    • size of the neighbour country (the bigger the better)
    • how many defenders (the fewer the better)

loop through every country the robot owns

      foreach (int countryId in RobotCountryIds) {
        var country = Map[countryId];

calculate how big is the attacking army

        double attackingArmies = country.ArmySize * Game.AttackFactor;

loop through every neighbour of this country. Remember the weakest enemy country compared to your attacking country.

        foreach (int neighbourId in country.NeighbourIds) {
          var neighbour = Map[neighbourId];
          if (
            !neighbour.IsMountain && //cannot attack mountain
            neighbour.OwnerId!=Player.Id && //don't attack own country
            neighbour.ArmySize<attackingArmies) //is robot strong enough ?
          {
            //calculate how much stronger the attacker is then the defender
            double newRatio = attackingArmies / neighbour.ArmySize * neighbour.Size;
            if (ratio<newRatio) {
              //found a weaker country to attack
              ratio = newRatio;
              attacker = country;
              target = neighbour;
            }
          }
        }
      }

Decide what will be your move after looping through all your countries.

      Move move;
      if (attacker==null) {

If a robot decides not to attack, he can select one country and move all armies from countries he owns around that country into that country. See BasicRobot.cs for an example of how this can be done.

SimpleRobot decides to skip one move if he cannot attack.

        move = Move.NoMove;
      } else {
        attackers.Clear();
        ////SimpleRobot is a bit stupid and uses only 1 of his countries to attack
        ////It would be better if he would use all of his countries which are neighbours of the attacked countries,
        ////like this:
        ////foreach (int targetNeighbourId in target.NeighbourIds) {
        ////  Country targetNeighbour = Map[targetNeighbourId];
        ////  if (targetNeighbour.OwnerId==Player.Id) {
        ////    attackers.Add(targetNeighbour);
        ////  }
        ////}
        attackers.Add(attacker);
        move = new Move(MoveTypeEnum.attack, Player, target!, attackers);
      }
      return move;
    }
  }
}

SimpleRobot is kept on purpose to the bare minimum. A bit more sophisticated is BasicRobot, which attacks an enemy country with all his own countries being neighbours of the enemy and when there is no good attack to perform, it moves his own armies neighbouring one of his countries into that country. This is rather important in the later phase of the game.

However, BasicRobot is lacking any strategic planning, like:

  • Detect attacks and defend against them
  • Once reaching a certain size, also grow armies in different countries instead of attacking as much as possible
  • Which attack improves the "shape" of his countries:
    • When his countries bild a "circle", the countries inside the circle have no enemies.
    • When he manages to get all countries in a column on the screen, less defence is needed.
  • Taking into account which is the weakest and strongest opponent
  • Preventing a strong neighbour from attacking by increasing the armies at the border
  • To tempt 2 enemies to attack each other: If the Robot owns a country surrounded by 2 enemies, that country prevents the enemies from fighting each other. If the Robot's country attacks an enemy country, it will most likely lose and the 2 enemies will start to attack each other.

When you play MasterGrab yourself, you will discover also other strategies, which you can then teach your Robot.

To my surprise, even BasicRobot has no strategy, it is actually difficult to beat. Meaning it should not be that hard for you to write an even better Robot.

About

MasterGrab is a WPF game where a human player plays against several computer players (=Robots). You can program your own Robot in C#.

License:Creative Commons Zero v1.0 Universal


Languages

Language:C# 97.8%Language:HTML 2.2%