oddsengine

package module
v0.0.0-...-76ef90c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 20, 2018 License: MIT Imports: 7 Imported by: 0

README

Axis and Allies Odds Calculator

Build Status

Introduction

The odds engine is a tool to calculate the odds of Axis and Allies conflicts. Currently supports the following games.

  • Axis and Allies 1941
  • Axis and Allies 1942 Second Edition
  • Axis and Allies 1940 (Pacific, Europe and Global) Second Edition

There is a simple interface for generating a summary of a conflict.


import (
    "fmt",
	"github.com/jmeyering/oddsengine",
)

func main() {
    attackers := map[string]int{"inf": 2, "art": 1, "fig": 2}
    defenders := map[string]int{"aaa": 1, "inf": 2, "tan": 1, "tac": 1}

    // Set the game that this conflict should run against. Default is "1940"
    oddsengine.SetGame("1940")

    // Set the number of iterations that the simulation will run the conflict
    // default is 1000
    oddsengine.SetIterations(10000)

    summary, err := oddsengine.GetSummary(attackers, defenders)

    if err != nil {
        panic(err)
    }

    fmt.Printf("%+v", summary)
    /*
    &{
        AverageRounds:2.73
        AttackerWinPercentage:59.91
        DefenderWinPercentage:35.01
        DrawPercentage:5.08
        AAAHitsAverage:0.34
        KamikazeHitsAverage:0
        AttackerAvIpcLoss:18.38
        DefenderAvIpcLoss:20.69
    }
    */
}

Unit Formation Mapping

Unit formations are maps of units to number of units, map[string]int to be precise. The map is formatted as unitalias: numunits. So map[string]int{"des":2, "bat":3} is a unit formation of 2 destroyers and 3 battleships. All units are aliased by taking the first 3 letters of their name.

Note:

Strategic bombers are identified by bom rather than str.

Unit Designations

There are a couple special designations that you can assign to a unit which will affect how it functions and/or its order or loss position.

Reserved

A reserved unit is designated by prefixing the unit alias with a "+". A reserved unit will be moved to the end of the order of loss. Meaning it will be taken last.

attackers := map[string]int{"inf": 2, "+tan": 1, "tan": 2, "fig": 2}

The above unit formation includes 3 tanks, with one of them identified as being "reserved". When taking losses, all non-reserved units will be taken before the reserved tank. So the Order of loss above will be []string{"inf", "tan", "fig", "+tan"}

Damaged

A damaged unit is designated by prefixing the unit alias with a "-". A damaged unit designation is a way to represent a damaged capital ship. In Axis and Allies 1940, Capital ships retain all damage until they are repaired at a naval base. When calculating odds for a battle involving damaged capital ships, it is important the system is able to differentiate between non damaged and damaged ships.

defenders := map[string]int{"des": 3, "-bat": 1, "-car": 1, "bat": 2}

The above unit formation includes 3 battleships, of which, 1 is damaged. It also contains 1 damaged carrier

Order of loss

Order of loss is a complicated matter to tackle. There are multiple ways units could be taken off the board, and accounting for real time situation assessment to optimize an attack is rather difficult, and out of the scope of what I'm attempting to accomplish.

For now, the engine uses a "cost" model of loss. Meaning that the more expensive a unit is in the game, the higher likelihood it will be taken last. I've found this to be generally a fine method of assigning loss. The only hiccups really come when dealing with defending bombers and in some sea battles, where you would really like to sacrifice some carriers and not take your cruisers or aircraft.

Potentially I may work in an attacker and defender specific "value" model of loss which will factor in both cost and hit value of the unit.

Note:

The current workaround to this problem is manually assigning reserved units.

Combined Arms

Combined arms are calculated appropriately for each game.

  • Infantry & Mechanized Infantry +1 attack when paired with Artillery
  • Tactical Bomber +1 attack when paired with a Tank or Fighter
  • Aircraft + Destroyer can hit subs

Special Combat

The engine appropriately calculates all types of special combat within the supported games.

  • AAA Defence
  • Submarine Surprise Attack
  • Kamikaze Strike
  • Offshore Bombardment
AAA Defence

AAA can simply be passed in as a defending unit and the engine will calculate the number of shots appropriately. Pass in the number of AAA Units not the number of AAA shots. The engine will appropriately calculate the number of shots factoring in both the number of defending AAA Units and the number of attacking aircraft. Casualties will be removed immediately without a chance to fire back.

attackers := map[string]int{"fig":1, "tac":1, "bom":1}
defenders := map[string]int{"aaa":1}

Will fire 3 AAA shots at the attacking aircraft

Submarine Surprise Attack

Submarines eligible to fire a surprise attack will do so, and any casualties will be removed immediately without a chance to fire back

Kamikaze Strike

Kamikaze strikes are passed in as a defending unit and the engine will calculate the strike appropriately. Pass through the number of tokens used as the unit number and the engine will calculate casualties. Casualties will be removed immediately without a chance to fire back.

defenders := map[string]int{"kam": 1, "des": 2, "sub": 2}

Offshore Bombardment

In order to run a simulation involving offshore bombardment, simply include all your ground/air units and valid bombard-able ships to the attackers unit formation. The simulation will appropriately calculate the bombard hits during the first round of conflict.

attackers := map[string]int{"inf": 3, "tan": 2, "tac": 2, "cru": 1, "bat": 2}

Will fire two offshore bombardment shots from the battleship.

Note:

According to the rules, offshore bombardment is limited to the number of units offloaded into the territory via transport. The engine assumes you have already done this calculation and will not limit the number of bombardments that have been passed in. See Caveats.

Caveats

Very little time was spent worrying about error handling in cases where using a little brain power works just fine.

For example within the game it is totally possible to have the following conflict:

attackers := map[string]int{"inf": 3}
defenders := map[string]int{"des": 4}

summary, err := oddsengine.GetSummary(attackers, defenders)

Of course this conflict is absurd. There is no circumstance where 3 attacking infantry could attack 4 defending destroyers.

The engine, however, will return you a summary of this conflict and not error. The onus is on the user to provide correct input in these cases. For the record, however, the destroyers win ~95% of the time.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func SetBaseOol

func SetBaseOol(ool []string)

SetBaseOol allow a custom baseOol to be set for the conflict.

func SetGame

func SetGame(g string)

SetGame sets the game up internally. Altering unit makeup, and ool

func SetIterations

func SetIterations(i int)

SetIterations changes the number of times the simulation will be ran.

func SetMustTakeTerritory

func SetMustTakeTerritory(a bool)

SetMustTakeTerritory toggles the mustTakeTerritory flag for the simulation

Types

type ByAttackingPower

type ByAttackingPower struct{ Units }

ByAttackingPower sorts the units by the higest Attack value of the unit

func (ByAttackingPower) Less

func (p ByAttackingPower) Less(i, j int) bool

Less implementing Sortable

type ByCost

type ByCost struct{ Units }

ByCost sorts the units by the lowest Cost value of the unit

func (ByCost) Less

func (p ByCost) Less(i, j int) bool

Less implementing Sortable

type ByDefendingPower

type ByDefendingPower struct{ Units }

ByDefendingPower sorts the units by the higest Defend value of the unit

func (ByDefendingPower) Less

func (p ByDefendingPower) Less(i, j int) bool

Less implementing Sortable

type ConflictProfile

type ConflictProfile struct {
	// Rounds is the number of rounds a conflict took
	Rounds int

	// DefenderHits is number of losses by round for the attacker
	DefenderHits []int

	// AttackerHits is number of losses by round for the defender
	AttackerHits []int

	// The number of IPC's that the attacker lost in the conflict
	AttackerIpcLoss int

	// The number of IPC's that the defender lost in the conflict
	DefenderIpcLoss int

	// Number of Attacking Units Remaining at the end of the Conflict
	AttackerUnitsRemaining []map[string]int

	// Number of Defending Units Remaining at the end of the Conflict
	DefenderUnitsRemaining []map[string]int

	// AAA Hits represent the number of AAA hits for the conflict
	AAAHits int

	// AAA Hits represent the number of Kamikaze hits for the conflict
	KamikazeHits int

	// Outcome represents the status of the conflict after the fact.
	//  1: Attacker Victory
	//  0: Draw
	// -1: Defender Victory
	Outcome int
}

ConflictProfile is a struct representing the outcome of a single conflict

type FirstRoundResult

type FirstRoundResult struct {
	AttackerHits int `json:"attackerHits"`
	DefenderHits int `json:"defenderHits"`
	Frequency    int `json:"frequency"`
	AttackerWin  int `json:"attackerWin"`
	DefenderWin  int `json:"defenderWin"`
	Draw         int `json:"draw"`
}

type FirstRoundResultCollection

type FirstRoundResultCollection []FirstRoundResult

func (FirstRoundResultCollection) Add

type InvalidUnitError

type InvalidUnitError struct {
	// contains filtered or unexported fields
}

InvalidUnitError represents an error where an invalid unit has been entered for a particular game.

func (InvalidUnitError) Error

func (i InvalidUnitError) Error() string

Error returns the string form of the error

type RollMap

type RollMap []RollValue

RollMap is the type that can hold multiple RollValue types

func (RollMap) AddRoll

func (r RollMap) AddRoll(hitValue, num int) (newMap RollMap)

AddRoll will add a specific RollValue to a RollMap based on a hitValue and num. returning the new RollMap with value added

func (RollMap) Get

func (r RollMap) Get(num int) (index int, rv *RollValue)

Get returns a the index of and the specific RollValue within the RollMap with a hitValue matching the passed in num

func (RollMap) HasValue

func (r RollMap) HasValue(num int) (has bool)

HasValue returns whether or not a RollMap has an entry for a RollValue with a hitValue matching the passed in num.

func (RollMap) Len

func (r RollMap) Len() int

Len return the length of the map

func (RollMap) Less

func (r RollMap) Less(i, j int) bool

Less show which rollmap is less than than another

func (RollMap) Reduce

func (r RollMap) Reduce(hitValue, num int) (newMap RollMap)

Reduce will reduce the num value of one RollValue within a RollMap. If a RollMap, for example, Contains a RollValue of {hitValue: 3, num:2} and we Reduce(3, 1). The resulting RollMap will contain {hitValue: 3, num: 1}. having reduced the number of rolls at the hitvalue of 3. The new RollMap will be returned by this function

func (RollMap) RemoveUnits

func (r RollMap) RemoveUnits(f map[string]int, units []string, mode string)

RemoveUnits reduces the roll map by the passed in slice of units for the given. mode. Uses the formation to determine what numbers to remove

func (RollMap) Swap

func (r RollMap) Swap(i, j int)

Swap change the position of two elements in the map

type RollValue

type RollValue struct {
	// contains filtered or unexported fields
}

RollValue simply defines the basic structure of a thing that can be rolled and assign hits to the opposing units. Given a hitValue (The number that counts as a "hit") and a num (The number of rolls at that number)

type Summary

type Summary struct {
	// TotalSimulations The number of simulations that have been ran
	TotalSimulations int `json:"totalSimulations"`

	// AverageRounds The number of rounds on average a conflict lasted.
	AverageRounds float64 `json:"averageRounds"`

	// AttackerWinPercentage The percentage of conflicts that the attacker won
	AttackerWinPercentage float64 `json:"attackerWinPercentage"`

	// DefenderWinPercentage The percentage of conflicts that the defender won
	DefenderWinPercentage float64 `json:"defenderWinPercentage"`

	// DrawPercentage The percentage of conflicts that was a draw
	DrawPercentage float64 `json:"drawPercentage"`

	// AAAHitsAverage The number of AAA hits per round on average
	AAAHitsAverage float64 `json:"aaaHitsAverage"`

	// KamikazeHitsAverage The number of kamikaze hits per round on average
	KamikazeHitsAverage float64 `json:"kamikazeHitsAverage"`

	// AttackerAvgIpcLoss The number of IPC's the attacker loses on average
	AttackerAvgIpcLoss float64 `json:"attackerAvgIpcLoss"`

	// DefenderAvgIpcLoss The number of IPC's the defender loses on average
	DefenderAvgIpcLoss float64 `json:"defenderAvgIpcLoss"`

	// FirstRoundResults is the array of first round data. Represents the
	// number of hits that an attacker and defender get on the first round,
	// the frequency of such a result, and the victory result of that conflict.
	FirstRoundResults FirstRoundResultCollection `json:"firstRoundResults"`

	// AttackerUnitsRemaining represents all the remaining units at the end
	// of conflict. The units are represented by a string and the number of
	// times that that formation remained at the end of the conflict is the
	// value
	AttackerUnitsRemaining map[string]int `json:"attackerUnitsRemaining"`

	// DefenderUnitsRemaining represents all the remaining units at the end
	// of conflict. The units are represented by a string and the number of
	// times that that formation remained at the end of the conflict is the
	// value
	DefenderUnitsRemaining map[string]int `json:"defenderUnitsRemaining"`
}

Summary is a type which represents the an averaged results of multiple conflicts.

func GetSummary

func GetSummary(attackers, defenders map[string]int) (*Summary, error)

GetSummary is the function that ties everything together, Returns a summary of the conflict.

type Unit

type Unit struct {
	Alias            string
	Name             string
	Cost             int
	Attack           int
	Defend           int
	IsShip           bool
	IsAAA            bool
	IsSub            bool
	IsAircraft       bool
	IsBunker         bool
	CapitalShip      bool
	CanBombard       bool
	CanTakeTerritory bool
	PlusOneRolls     func(map[string]int) int
	PlusOneDefend    func(map[string]int) int
	// Number of units receiving a defensive boost if this is a bunker
	Capacity int
	// MultiRoll is the number of dice the unit can roll, and select the best
	// roll for it's hit.
	MultiRoll int
}

Unit represents a specific unit within a game, identifing a lot of specific information related to the unit.

type Units

type Units []Unit

Units is a container for multiple Unit structs

func (Units) Delete

func (p Units) Delete(alias string) Units

Delete removes an item from a units type and returns the new type

func (Units) Find

func (p Units) Find(alias string) *Unit

Find a specific unit, by alias, within the slice of Units

func (Units) HasUnit

func (p Units) HasUnit(alias string) (has bool)

HasUnit tells us if a Units type has a unit identified by the alias provided

func (Units) Len

func (p Units) Len() int

Len implementing Sortable

func (Units) Swap

func (p Units) Swap(i, j int)

Swap implementing Sortable

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL