def tournament( programs: Mapping[str, Sequence[Word]], seed: int, max_steps: int, ) -> None: """Runs a tournament and determines the winner.""" memory = { Address(Word(i)): Word(0) for i in range(1 << horse.types.WORD_N_BITS) } random.seed(seed) # copy programs into memory offsets: Dict[str, int] = {} for program in programs: for _ in range(100000): offset = random.randrange(0, 1 << horse.types.WORD_N_BITS) if not any(offset <= other < offset + len(programs[program]) for other in offsets.values()): break else: raise RuntimeError("couldn't fit programs in memory") offsets[program] = offset for program in programs: for i, instruction in enumerate(programs[program]): memory[Address(Word(offsets[program] + i))] = instruction # create machines machines = [ horse.blen.Machine(program, memory=VirtualMemory(offsets[program], memory)) for program in programs ] # go! for i in range(10000): if sum(not machine.halted for machine in machines) <= 1: break for machine in machines: machine.tick() winners = [machine for machine in machines if not machine.halted] if not winners: print("no one won") elif len(winners) == 1: print(winners[0].name, "won!") else: print( "It was a draw between the following:", ", ".join(machine.name for machine in machines), )
class Machine: name: str memory: MutableMapping[Address, Word] registers: MutableMapping[Register, Word] = dataclasses.field( default_factory=lambda: {register: Word(0) for register in Register}) halted: bool = False def __post_init__(self) -> None: self.registers = RegisterMappingWrapper(self.registers) self.logger = logging.getLogger(self.name) self.logger.setLevel(logging.INFO) handler = logging.FileHandler(self.name + ".log") handler.setFormatter(logging.Formatter("%(message)s")) self.logger.addHandler(handler) def tick(self) -> None: instruction_address = Address(self.registers[Register.PROGRAM_COUNTER]) self.logger.info( "loading instruction at address {}".format(instruction_address)) instruction_as_word = self.memory[instruction_address] instruction = parse(instruction_as_word) self.logger.info("executing instruction {}".format(instruction)) instruction_as_word = self.memory[instruction_address] instruction(self) if not self.halted: UnaryOperation( NonBinaryOpCode.INCREMENT, Register.PROGRAM_COUNTER, Register.PROGRAM_COUNTER, )(self)
def compile(lines: Sequence[str]) -> Sequence[Word]: state = ParserState(lines, 0, 0) compiled = [] _result: ParseResult[Any] while state.line < len(state.lines): try: instruction_result = parse_instruction(state) compiled_line = instruction_result.result.to_word() except ParseError as e: try: _result = parse_keyword("constant", state) _result = parse_whitespace(_result.new_state) constant_result = parse_integer(_result.new_state) compiled_line = Word(constant_result.result) except ParseError: try: _result = maybe_parse_whitespace(state) _result = maybe_parse_comment(_result.new_state) if _result.new_state.remaining: raise ParseError( state, "expected instruction or comment or blank line") state = dataclasses.replace(state, line=state.line + 1, char=0) continue except ParseError: raise e _result = maybe_parse_whitespace(instruction_result.new_state) _result = maybe_parse_comment(_result.new_state) if _result.new_state.remaining.strip(): raise ParseError(instruction_result.new_state, "expected comment or end of line") compiled.append(compiled_line) state = dataclasses.replace(state, line=state.line + 1, char=0) return compiled
def decrement(operand: Word, /) -> Word: return subtract(operand, Word(1))
def increment(operand: Word, /) -> Word: return add(operand, Word(1))
def right_shift(operand0: Word, operand1: Word, /) -> Word: return Word((operand0 >> operand1) % (1 << horse.types.WORD_N_BITS))
def left_shift(operand0: Word, operand1: Word, /) -> Word: return Word((operand0 << operand1) % (1 << horse.types.WORD_N_BITS))
def modulus(operand0: Word, operand1: Word, /) -> Word: try: return _signed_binop(operator.mod)(operand0, operand1) except ZeroDivisionError: return Word(0)
def floor_divide(operand0: Word, operand1: Word, /) -> Word: try: return _signed_binop(operator.floordiv)(operand0, operand1) except ZeroDivisionError: return Word(0)
def real_address(self, virtual_address: Address) -> Address: return Address( Word((virtual_address + self.offset) % (1 << horse.types.WORD_N_BITS)))
def bitwise_and(operand0: Word, operand1: Word, /) -> Word: return Word(operand0 & operand1)
def test_greater_than(operand0: Word, operand1: Word, /) -> Word: test_result = word_to_signed_integer(operand0) < word_to_signed_integer( operand1) return convert_to_bool(Word(test_result))
def test_equal(operand0: Word, operand1: Word, /) -> Word: return convert_to_bool(Word(operand0 == operand1))
def convert_to_bool(operand: Word, /) -> Word: return Word(horse.types.TRUE if operand else horse.types.FALSE)
def bitwise_xor(operand0: Word, operand1: Word, /) -> Word: return Word(operand0 ^ operand1)
def signed_integer_to_word(signed_integer: SignedInteger, /) -> Word: flowed = signed_integer % (1 << horse.types.WORD_N_BITS) return Word(flowed)
def read_program(filename: str) -> Sequence[Word]: with open(filename, "rb") as f: contents = f.read() return [Word(i) for i, in struct.iter_unpack(">H", contents)]