diff --git a/src/simulations/corewar/corewar/core.ts b/src/simulations/corewar/corewar/core.ts new file mode 100644 index 0000000..627721b --- /dev/null +++ b/src/simulations/corewar/corewar/core.ts @@ -0,0 +1,18 @@ +import { CoreMemory } from "./instruction" +import { Dat } from "./instructions" + +export class Core { + public readonly core: CoreMemory[] + + public constructor( + public readonly coreSize = 80, + ) { + this.core = new Array(coreSize).fill(Dat) + } + + public write(memory: CoreMemory[], atIndex: number): void { + memory.forEach((memoryValue, index) => { + this.core[(atIndex + index) % this.coreSize] = memoryValue + }) + } +} \ No newline at end of file diff --git a/src/simulations/corewar/corewar/corewar.ts b/src/simulations/corewar/corewar/corewar.ts new file mode 100644 index 0000000..4d1b620 --- /dev/null +++ b/src/simulations/corewar/corewar/corewar.ts @@ -0,0 +1,3 @@ +import * as Corewar from "./index" + +export { Corewar } \ No newline at end of file diff --git a/src/simulations/corewar/corewar/engine.ts b/src/simulations/corewar/corewar/engine.ts new file mode 100644 index 0000000..435aa24 --- /dev/null +++ b/src/simulations/corewar/corewar/engine.ts @@ -0,0 +1,72 @@ +import { Core } from "./core" +import { interpret } from "./interpreter" +import { Warrior } from "./warrior" +import { WarriorCode } from "./warrior_code" + + +export class Engine { + public get tick(): number { + return this._tick + } + + private _tick = 0 + + public constructor( + public readonly core: Core, + private readonly warriors: Warrior[], + ) { + } + + public runTick(): void { + this.warriors.forEach(warrior => { + const pointerWrapper = warrior.getNextPointer() + if (pointerWrapper == null) { + return + } + + const result = interpret(this.core, pointerWrapper.pointer) + pointerWrapper.tickFinished(result) + }) + + this._tick += 1 + } +} + + +export class EngineSetup { + private readonly core: Core + private readonly warriorCode = new Map<string, WarriorCode>() + + public constructor( + coreSize = 80, + ) { + this.core = new Core(coreSize) + } + + public addWarriorCode(warriorCode: WarriorCode): void { + this.warriorCode.set(warriorCode.name, warriorCode) + } + + public init(): Engine | string { + const initialCodeMemoryLength = Array.from(this.warriorCode.values()).reduce((result, current) => result + current.code.length, 0) + const bufferSize = this.core.coreSize - initialCodeMemoryLength + if (bufferSize < 0) { + return `Coresize too small (initial warriors requires ${initialCodeMemoryLength} but ${this.core.coreSize} coresize given)` + } + + const warriors: Warrior[] = [] + const addWarrior = (instructionPointer: number, warriorCode: WarriorCode): void => { + warriors.push(new Warrior(warriorCode, instructionPointer)) + } + + let instructionPointer = 0 + const bufferForEachWarriors = Math.floor(bufferSize / this.warriorCode.size) + this.warriorCode.forEach(warriorCode => { + addWarrior(instructionPointer, warriorCode) + instructionPointer += warriorCode.code.length + bufferForEachWarriors + }) + + return new Engine(this.core, warriors) + } +} + diff --git a/src/simulations/corewar/corewar/index.ts b/src/simulations/corewar/corewar/index.ts new file mode 100644 index 0000000..9cb8389 --- /dev/null +++ b/src/simulations/corewar/corewar/index.ts @@ -0,0 +1 @@ +export * from "./engine" diff --git a/src/simulations/corewar/corewar/instruction.ts b/src/simulations/corewar/corewar/instruction.ts new file mode 100644 index 0000000..216e426 --- /dev/null +++ b/src/simulations/corewar/corewar/instruction.ts @@ -0,0 +1,94 @@ +const opecodes = [ + "add", + "mov", + "jmp", + "jmz", + "dat", +] as const +export type RawOpecode = typeof opecodes[number] + + +const modifiers = [ + ".a", + ".b", + ".ab", + ".ba", +] as const +export type Modifier = typeof modifiers[number] + +export type OperandType = ".a" | ".b" + + +export type Opecode = { + readonly opecode: RawOpecode + readonly modifier: Modifier +} + + +const addressingModes = [ + "#", // Immediate + // "$", // Direct + // "*", // A Indirect + // "@", // B Indirect + // "{", // A Pre-decrement Indirect + // "}", // A Post-increment Indirect + // "<", // B Pre-decrement Indirect + // ">", // B Post-increment Indirect +] as const +export type AddressingMode = typeof addressingModes[number] + +export type Operand = { + operand: number + readonly addressingMode: AddressingMode +} + + +export type Instruction = { + readonly opecode: Opecode + + readonly operandA: Operand + readonly operandB: Operand +} +export type Data = number + +export type CoreMemory = Instruction | Data + +export const cloneMemory = (memory: CoreMemory): CoreMemory => { + if (isInstruction(memory)) { + return {...memory} + } + return memory +} + +export const isInstruction = (memory: CoreMemory): memory is Instruction => !(typeof memory === "string") + +export const isZeroValue = (memory: CoreMemory): boolean => { + if (isInstruction(memory)) { + return memory.operandA.operand === 0 && memory.operandB.operand === 0 + } + return memory === 0 +} + +export const compute = (memory: CoreMemory, computation: (value: number) => number, operandType: OperandType): CoreMemory => { + if (!isInstruction(memory)) { + return computation(memory) + } + + switch (operandType) { + case ".a": + memory.operandA.operand = computation(memory.operandA.operand) + return memory + case ".b": + memory.operandB.operand = computation(memory.operandB.operand) + return memory + } +} + + +export type ExecutionResult = { + readonly result: "succeeded" + readonly newPointer: number +} | { + readonly result: "failed" + readonly failureReason: "dat" | "raw data" +} \ No newline at end of file diff --git a/src/simulations/corewar/corewar/instructions.ts b/src/simulations/corewar/corewar/instructions.ts new file mode 100644 index 0000000..5f975f1 --- /dev/null +++ b/src/simulations/corewar/corewar/instructions.ts @@ -0,0 +1,16 @@ +import { Instruction } from "./instruction" + +export const Dat: Instruction = { + opecode: { + opecode: "dat", + modifier: ".a", + }, + operandA: { + addressingMode: "#", + operand: 0, + }, + operandB: { + addressingMode: "#", + operand: 0, + }, +} \ No newline at end of file diff --git a/src/simulations/corewar/corewar/interpreter.ts b/src/simulations/corewar/corewar/interpreter.ts new file mode 100644 index 0000000..733c942 --- /dev/null +++ b/src/simulations/corewar/corewar/interpreter.ts @@ -0,0 +1,69 @@ +import { Core } from "./core" +import { cloneMemory, compute, ExecutionResult, isInstruction, isZeroValue } from "./instruction" + +export const interpret = (core: Core, pointer: number): ExecutionResult => { + const instruction = core.core[pointer] + if (!isInstruction(instruction)) { + return { + result: "failed", + failureReason: "raw data", + } + } + + const p = (relativePointer: number): number => (pointer + relativePointer) % core.coreSize + + switch (instruction.opecode.opecode) { + case "add": + // .ab実装 + core.core[p(instruction.operandA.operand)] = compute(core.core[p(instruction.operandA.operand)], value => value + instruction.operandB.operand, ".b") + return { + result: "succeeded", + newPointer: p(+1), + } + + case "mov": + core.core[p(instruction.operandB.operand)] = cloneMemory(core.core[p(instruction.operandA.operand)]) + return { + result: "succeeded", + newPointer: p(+1), + } + + case "jmp": + return { + result: "succeeded", + newPointer: p(instruction.operandA.operand), + } + + case "jmz": + if (isZeroValue(core.core[p(instruction.operandB.operand)])) { + return { + result: "succeeded", + newPointer: p(instruction.operandA.operand), + } + } + return { + result: "succeeded", + newPointer: p(+1), + } + + case "dat": + return { + result: "failed", + failureReason: "dat", + } + } +} + +/* +// Bomber +start add.ab #4, bmb + mov.i bmb, @bmb + jmp start +bmb dat #0, #0 + +// Scanner +scn add #10, ptr +ptr jmz.f scn, 5 + mov.i 2, >ptr + jmp -1 +*/ \ No newline at end of file diff --git a/src/simulations/corewar/corewar/warrior.ts b/src/simulations/corewar/corewar/warrior.ts new file mode 100644 index 0000000..15e525d --- /dev/null +++ b/src/simulations/corewar/corewar/warrior.ts @@ -0,0 +1,69 @@ +import { ExecutionResult } from "./instruction" +import { WarriorCode } from "./warrior_code" + + +type PointerWrapper = { + readonly pointer: number + readonly tickFinished: (result: ExecutionResult) => void +} + + +class WarriorProcess { + public constructor( + public instructionPointer: number, + ) { + } +} + + +export class Warrior { + public get name(): string { + return this.code.name + } + private processPointer = 0 + private aliveProcesses: WarriorProcess[] = [] + private deadProcesses: WarriorProcess[] = [] + + public constructor( + public readonly code: WarriorCode, + instructionPointer: number, + ) { + this.aliveProcesses.push(new WarriorProcess(instructionPointer)) + } + + public getNextPointer(): PointerWrapper | null { + const process = this.getNextAliveProcess() + if (process == null) { + return null + } + + return { + pointer: process.instructionPointer, + tickFinished: result => { + switch (result.result) { + case "succeeded": + process.instructionPointer = result.newPointer + this.processPointer = (this.processPointer + 1) % this.aliveProcesses.length + break + case "failed": + this.aliveProcesses.splice(this.processPointer, 1) + this.deadProcesses.push(process) + if (this.aliveProcesses.length > 0) { + this.processPointer = this.processPointer % this.aliveProcesses.length + } + break + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _: never = result + break + } + } + }, + } + } + + /** @throws */ + private getNextAliveProcess(): WarriorProcess | null { + return this.aliveProcesses[this.processPointer] + } +} \ No newline at end of file diff --git a/src/simulations/corewar/corewar/warrior_code.ts b/src/simulations/corewar/corewar/warrior_code.ts new file mode 100644 index 0000000..1d9a838 --- /dev/null +++ b/src/simulations/corewar/corewar/warrior_code.ts @@ -0,0 +1,6 @@ +import { Instruction } from "./instruction" + +export type WarriorCode = { + readonly name: string + readonly code: Instruction[] +} \ No newline at end of file