Skip to content

Commit

Permalink
基本的なopecodeを実装
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuyoshi-yamazaki committed Dec 1, 2024
1 parent bf9d23c commit c388542
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/simulations/corewar/corewar/core.ts
Original file line number Diff line number Diff line change
@@ -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
})
}
}
3 changes: 3 additions & 0 deletions src/simulations/corewar/corewar/corewar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as Corewar from "./index"

export { Corewar }
72 changes: 72 additions & 0 deletions src/simulations/corewar/corewar/engine.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}

1 change: 1 addition & 0 deletions src/simulations/corewar/corewar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./engine"
94 changes: 94 additions & 0 deletions src/simulations/corewar/corewar/instruction.ts
Original file line number Diff line number Diff line change
@@ -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"
}
16 changes: 16 additions & 0 deletions src/simulations/corewar/corewar/instructions.ts
Original file line number Diff line number Diff line change
@@ -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,
},
}
69 changes: 69 additions & 0 deletions src/simulations/corewar/corewar/interpreter.ts
Original file line number Diff line number Diff line change
@@ -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
*/
69 changes: 69 additions & 0 deletions src/simulations/corewar/corewar/warrior.ts
Original file line number Diff line number Diff line change
@@ -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]
}
}
6 changes: 6 additions & 0 deletions src/simulations/corewar/corewar/warrior_code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Instruction } from "./instruction"

export type WarriorCode = {
readonly name: string
readonly code: Instruction[]
}

0 comments on commit c388542

Please sign in to comment.