gmars

package module
v0.1.14 Latest Latest
Warning

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

Go to latest
Published: Dec 6, 2024 License: MIT Imports: 10 Imported by: 0

README

gMARS

Go Reference Go Report Card

gMARS is an implementation of a Core War Memory Array Redcode Simulator (MARS) written in Go.

vmars screenshot

In the the game of Core War, two virus-like programs written in assembly fight against each other in the memory of a Simulator where they are able to write and execute new code and modify eachother. For more information about Core War see:

  • Wikipedia: CoreWar entry
  • corewar.co.uk: John Metcalf's Core War Site with tutorials, history, and links.
  • KOTH.org: A King of the Hill server with ongoing competitive matches, information, and links.
  • Koenigstuhl: An 'infinite hill' site that collects warriors and publishes their rankings and source code.

Why another MARS?

gMARS was created to provide a reference implementation of a MARS in Go. There are many other implementations but I wanted to meet the following requirements:

  1. A thread-safe library implementing the MARS mechanics.
  2. Strong compliance to standards, with R/W limits and '88 rules enforcment.
  3. Reporting hooks to support custom front-ends and analysis engines.
  4. A modern visual simulator front-end.

Running the Simulator

Currently only two warrior matches are supported and the warrior files must be supplied as command line arguments. Both versions also accept the following arguments

  -preset string
        Load named preset config (and ignore other flags)
  -8    Enforce ICWS'88 rules
  -F int
        fixed position of warrior #2
  -c int
        Cycles until tie (default 80000)
  -debug
        Dump verbose debug information
  -l int
        Max. warrior length (default 100)
  -p int
        Max. Processes (default 8000)
  -r int (CLI only)
        Rounds to play (default 1)
  -s int
        Size of core (default 8000)
Presets

You can use the -preset <name> flag to use a named presed configuration. If a preset is loaded, the other flags are not parsed and applied.

Name Simulator Mode CoreSize Length Processes Cycles
nop94 NOP94 8000 100 8000 80000
88 ICWS88 8000 100 8000 80000
icws ICWS88 8192 300 8000 100000
noptiny NOP94 800 20 800 8000
nop256 NOP94 256 10 60 2560
nopnano NOP94 80 5 80 800
Visual MARS Controls

Keyboard controls:

  • Space to start/pause the simulation
  • Up/Down to increase or decrease simulation speed
  • Left/Right to stop or step forward one frame of the simulation (at the visualizer speed)
  • R to reset the simulator with the next starting position
  • Escape to quit

Extra Arguments:

  • -showread: Enable recording and rendering of CoreRead states.
CLI MARS

When the simulation completes in the gmars CLI, a line is printed for each warrior with the number of wins and ties:

1 0
0 0 

Implemented Features

  • Compilation of code compliant with the ICWS'94 standard specification (favoring pMARS compatibility when applicable)
  • ICWS'88 compilation mode to enforce valid code generation.
  • Simulation of two warrior battles
  • Read/write limits (implemented, but not thoroughly tested)
  • Hooks generating updates for visualization and analysis
  • Visual MARS with interactive keyboard controls

Planned Features

  • P-Space support
  • Interactive debugger
  • Round robin and benchmark modes

Language Support

I have implemented the ICWS'94 Draft Standard to the best of my ability and added the following modifications based pMARS and other simulators:

Empty Fields

In the draft standard, if only a single operand is applied, it is placed in the B-Field for DAT instructions, or otherwise placed in the A-Field. In both cases #0 is supposed to be placed in the remaining field.

I found that corewin and pMARS load $0 into the B-Field when the instruction opcode is not DAT. Since this can cause divergent outcomes, I chose to follow other simulators for compatibility.

FOR/ROF Macros

FOR and ROF pseudo-opcodes have been added including labels and embedded for loops.

<start_ref...> <count_var> FOR <count_expr>
...
ROF

The count variable starts at 1 and increments for each following line until it equals the value of the count expression. Other labels added before the count variable name will be evaluated as line references to the first line emitted by the macro.

Variable concatenation with the & pseudo op is not implemented but planned.

Start Address Example
start i FOR 2
DAT start, i
ROF

Compiles to:

DAT.F  $  0, $  1
DAT.F  $ -1, $  2
Embedded Loop Example
i FOR 2
j FOR 2
DAT i, j
ROF
ROF

Compiles to:

DAT.F  $  1, $  1
DAT.F  $  1, $  2
DAT.F  $  2, $  1
DAT.F  $  2, $  2
Non-Supported Extensions

pMARS allows assignment to variables with a syntax appearing as (a=CORESIZE/2). This feature is not currently implemented, and I am not sure if I will plan to do so.

Testing Status / Known Bugs

The compiler is more subjective and still going through testing and active development, but the backend has been tested fairly thoroughly by running compiled load code and comparing outcomes to pMARS.

Load code tests were done with the 94nop and 88 Koenigstuhl hills. I found a single warrior Rush (11,1) that has inconsistent outcomes, which I am stil investigating.

Documentation

Overview

Package mars implements interfaces for MARS simulators and helper functions to define configuration and load warrior files.

The Simulator interface provides a MARS implemenation for running simulations, and the ReportingSimulator interface adds the Addreporter method to inject Reporter interfaces to receive callbacks to report state changes in the simulation.

RedCode files are first loaded as WarriorData{} structs, holding the data needed to create a Warrior inside a Simulator. These can safely be reused to create multiple Simulators concurrently.

The SimulatorConfig struct is provided to define configuration when creating Simulator instances, and compiling warriors.

Index

Constants

This section is empty.

Variables

View Source
var (
	ConfigKOTH88 = SimulatorConfig{
		Mode:       ICWS88,
		CoreSize:   8000,
		Processes:  8000,
		Cycles:     80000,
		ReadLimit:  8000,
		WriteLimit: 8000,
		Length:     100,
		Distance:   100,
	}
	ConfigICWS88 = SimulatorConfig{
		Mode:       ICWS88,
		CoreSize:   8192,
		Processes:  8000,
		Cycles:     10000,
		ReadLimit:  8000,
		WriteLimit: 8000,
		Length:     300,
		Distance:   100,
	}
	ConfigNOP94 = SimulatorConfig{
		Mode:       ICWS94,
		CoreSize:   8000,
		Processes:  8000,
		Cycles:     80000,
		ReadLimit:  8000,
		WriteLimit: 8000,
		Length:     100,
		Distance:   100,
	}
	ConfigNopTiny = SimulatorConfig{
		Mode:       NOP94,
		CoreSize:   800,
		Processes:  800,
		Cycles:     8000,
		ReadLimit:  800,
		WriteLimit: 800,
		Length:     20,
		Distance:   20,
	}
	ConfigNop256 = SimulatorConfig{
		Mode:       NOP94,
		CoreSize:   256,
		Processes:  60,
		Cycles:     2560,
		ReadLimit:  800,
		WriteLimit: 800,
		Length:     10,
		Distance:   10,
	}
	ConfigNopNano = SimulatorConfig{
		Mode:       NOP94,
		CoreSize:   80,
		Processes:  80,
		Cycles:     800,
		ReadLimit:  80,
		WriteLimit: 80,
		Length:     5,
		Distance:   5,
	}
)
View Source
var (
	//go:embed warriors/88/imp.red
	Imp_88_red []byte

	//go:embed warriors/94/imp.red
	Imp_94_red []byte

	//go:embed warriors/94/simpleshot.red
	SimpleShot_94_red []byte

	//go:embed warriors/94/bombspiral.red
	BombSpiral_94_red []byte
)

Functions

func ExpandAndEvaluate added in v0.1.14

func ExpandAndEvaluate(expr []token, symbols map[string][]token) (int, error)

func ForExpand added in v0.1.14

func ForExpand(lex tokenReader, symbols map[string][]token) ([]token, error)

func LexInput added in v0.1.14

func LexInput(r io.Reader) ([]token, error)

func ScanInput added in v0.1.14

func ScanInput(lex tokenReader) (map[string][]token, bool, error)

Types

type Address

type Address uint64

type AddressMode

type AddressMode uint8
const (
	DIRECT      AddressMode = iota // "$" direct reference to another address
	IMMEDIATE                      // "#" use the immediate value of this instruction
	A_INDIRECT                     // "*" use the A-Field of the address referenced by a pointer
	B_INDIRECT                     // "@" use the B-Field of the address referenced by a pointer
	A_DECREMENT                    // "{" use the A-field of the address referenced by a pointer, after decrementing
	B_DECREMENT                    // "<" use the B-field of the address referenced by a pointer, after decrementing
	A_INCREMENT                    // "}" use the A-field of the address referenced by a pointer, before incrementing
	B_INCREMENT                    // ">" use the B-field of the address referenced by a pointer, before incrementing
)

func (AddressMode) String

func (m AddressMode) String() string

String returns the single character string representation of an AddressMode, or "?"

type CoreState

type CoreState uint8
const (
	CoreEmpty CoreState = iota
	CoreExecuted
	CoreWritten
	CoreIncremented
	CoreDecremented
	CoreRead
	CoreTerminated
)

type Instruction

type Instruction struct {
	Op     OpCode
	OpMode OpMode
	A      Address
	AMode  AddressMode
	B      Address
	BMode  AddressMode
}

Instruction represents the raw values of a memory address

func (Instruction) NormString

func (i Instruction) NormString(coresize Address) string

NormString returns the decompiled instruction with signed field values normalized to a core size.

func (Instruction) String

func (i Instruction) String() string

String returns the decompiled instruction with unsigned field values

type OpCode

type OpCode uint8
const (
	DAT OpCode = iota
	MOV
	ADD
	SUB
	MUL
	DIV
	MOD
	CMP
	SEQ
	SNE
	SLT
	JMP
	JMZ
	JMN
	DJN
	SPL
	NOP
)

func (OpCode) String

func (o OpCode) String() string

type OpMode

type OpMode uint8
const (
	F OpMode = iota
	A
	B
	AB
	BA
	X
	I
)

func (OpMode) String

func (m OpMode) String() string

String returns the string representation of an OpMode, or "?"

type Report

type Report struct {
	Type         ReportType
	Cycle        int
	WarriorIndex int
	Address      Address
}

type ReportType

type ReportType uint8
const (
	SimReset ReportType = iota
	CycleStart
	CycleEnd
	WarriorSpawn
	WarriorTaskPop
	WarriorTaskPush
	WarriorTaskTerminate
	WarriorTerminate
	WarriorRead
	WarriorWrite
	WarriorDecrement
	WarriorIncrement
)

type Reporter

type Reporter interface {
	Report(r Report)
}

func NewDebugReporter

func NewDebugReporter(s Simulator) Reporter

type ReportingSimulator

type ReportingSimulator interface {
	Simulator
	AddReporter(r Reporter)
}

func NewReportingSimulator

func NewReportingSimulator(config SimulatorConfig) (ReportingSimulator, error)

type Simulator

type Simulator interface {
	CoreSize() Address
	CycleCount() int
	MaxCycles() int
	AddWarrior(data *WarriorData) (Warrior, error)
	GetWarrior(wi int) Warrior
	SpawnWarrior(wi int, startOffset Address) error
	Run() []bool

	// RunCycle runs a full cyle of the living warriors, starting at s,warriorIndex.
	//
	// If s.cycleCount > s.maxCycles or there are no living warriors, no warrior
	// will be run, s.cycleCount will not be incremented, and the return value will
	// be 0. Otherwise s.cycleCount is incremented and
	RunCycle() int
	GetMem(a Address) Instruction
	Reset()

	WarriorLivingCount() int
	WarriorCount() int
}

func NewSimulator

func NewSimulator(config SimulatorConfig) (Simulator, error)

type SimulatorConfig

type SimulatorConfig struct {
	Mode       SimulatorMode
	CoreSize   Address
	Processes  Address
	Cycles     Address
	ReadLimit  Address
	WriteLimit Address
	Length     Address
	Distance   Address
}

func NewQuickConfig

func NewQuickConfig(mode SimulatorMode, coreSize, processes, cycles, length Address) SimulatorConfig

func PresetConfig added in v0.1.11

func PresetConfig(name string) (SimulatorConfig, error)

func (SimulatorConfig) Validate

func (c SimulatorConfig) Validate() error

type SimulatorMode

type SimulatorMode uint8
const (
	ICWS88 SimulatorMode = iota
	NOP94
	ICWS94
)

type SimulatorState

type SimulatorState uint8
const (
	Initialized SimulatorState = iota
	Running
	Complete
)

type StateRecorder

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

StateRecorder implements a Reporter which records the most recent operation performed each core address and the warrior index associated. The initial state of each address is CoreEmpty with a warrior index of -1.

func NewStateRecorder

func NewStateRecorder(sim ReportingSimulator) *StateRecorder

func (*StateRecorder) GetMemState

func (r *StateRecorder) GetMemState(a Address) (CoreState, int)

func (*StateRecorder) Report

func (r *StateRecorder) Report(report Report)

func (*StateRecorder) SetRecordRead added in v0.1.9

func (r *StateRecorder) SetRecordRead(val bool)

type Warrior

type Warrior interface {
	Alive() bool
	Name() string
	Author() string
	Length() int
	Queue() []Address
	NextPC() (Address, error)
	LoadCode() string
}

type WarriorData

type WarriorData struct {
	Name     string        // Warrior Name
	Author   string        // Author Name
	Strategy string        // Strategy including multiple lines
	Code     []Instruction // Program Instructions
	Start    int           // Program Entry Point
}

func CompileWarrior added in v0.1.9

func CompileWarrior(r io.Reader, config SimulatorConfig) (WarriorData, error)

func ParseLoadFile

func ParseLoadFile(reader io.Reader, simConfig SimulatorConfig) (WarriorData, error)

func (*WarriorData) Copy

func (w *WarriorData) Copy() *WarriorData

Copy creates a deep copy of a WarriorData object

type WarriorState

type WarriorState uint8
const (
	WarriorAdded WarriorState = iota
	WarriorAlive
	WarriorDead
)

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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