import { integerLength , numAt } from '@lib/functions' ; import { Instruction , toInstruction } from './instruction.enum' ; import { Mode } from './mode.enum' ; export class IntCodeComputer implements Iterable < number | undefined > { public tape : Map < number , number > ; public cursor = 0 ; public relBase = 0 ; private halt = false ; public inputQueue ? : number [ ] ; public inputCallback ? : ( ) => number ; public outputCallback ? : ( output : number ) => number ; public constructor ( tape ) { this . tape = tape . reduce ( ( m , n , i ) => m . set ( i , n ) , new Map ( ) ) ; } public * iter ( ) { for ( const res of this ) { if ( res !== undefined ) { yield res ; } } } public * stepper ( ) { yield * this ; } public set input ( input ) { if ( typeof input === 'number' ) { this . inputQueue = [ input ] ; } else if ( typeof input === 'function' ) { this . inputCallback = input ; } else { this . inputQueue = input ; } } public withInput ( input ) { this . input = input ; return this ; } public withInputCallback ( inputCallback ) { this . inputCallback = inputCallback ; return this ; } public withOutputCallback ( outputCallback ) { this . outputCallback = outputCallback ; return this ; } public isHalt ( ) { return this . halt ; } public pushInput ( ... input ) { if ( ! this . inputQueue ) { this . inputQueue = [ ] ; } this . inputQueue . push ( ... input ) ; return this ; } public pushAsciiInput ( input , nl = true ) { if ( nl ) input += '\n' ; return this . pushInput ( ... [ ... input ] . map ( ( s ) => s . charCodeAt ( 0 ) ) ) ; } public pushInputIfEmpty ( input ) { if ( ! this . inputQueue || this . inputQueue . length === 0 ) { this . pushInput ( input ) ; return true ; } else return false ; } private getValue ( pos , mode = Mode . POS , asIndex = false ) { let v ; switch ( mode ) { case Mode . POS : v = this . tape . get ( pos ) || 0 ; break ; case Mode . REL : v = this . relBase + ( this . tape . get ( pos ) || 0 ) ; break ; case Mode . VAL : default : v = pos ; break ; } return asIndex ? v : this . tape . get ( v ) || 0 ; } public reset ( tape ? ) { if ( tape ) { this . tape = tape . reduce ( ( m , n , i ) => m . set ( i , n ) , new Map ( ) ) ; } this . cursor = 0 ; this . relBase = 0 ; this . halt = false ; return this ; } public peek ( at ) { return this . tape . get ( at ) ; } public set noun ( noun ) { this . tape . set ( 1 , noun ) ; } public withNoun ( noun ) { this . noun = noun ; return this ; } public set verb ( verb ) { this . tape . set ( 2 , verb ) ; } public withVerb ( verb ) { this . verb = verb ; return this ; } private getArg ( v , n , asIndex = false , mode ? ) { return this . getValue ( this . cursor + n + 1 , mode === undefined ? this . getMode ( v , n ) : mode , asIndex ) ; } private getMode ( v , n ) { return numAt ( v , integerLength ( v ) - n - 3 ) ; } public * [ Symbol . iterator ] ( ) { do { const v = this . tape . get ( this . cursor ) || 0 ; const i = toInstruction ( v ) ; switch ( i ) { case Instruction . ADD : this . addOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) , this . getArg ( v , 2 , true ) ) ; break ; case Instruction . MUL : this . mulOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) , this . getArg ( v , 2 , true ) ) ; break ; case Instruction . IN : this . inOp ( this . getArg ( v , 0 , true ) ) ; yield undefined ; break ; case Instruction . OUT : yield this . outOp ( this . getArg ( v , 0 ) ) ; break ; case Instruction . JIT : this . jitOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) ) ; break ; case Instruction . JIF : this . jifOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) ) ; break ; case Instruction . LT : this . ltOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) , this . getArg ( v , 2 , true ) ) ; break ; case Instruction . EQ : this . eqOp ( this . getArg ( v , 0 ) , this . getArg ( v , 1 ) , this . getArg ( v , 2 , true ) ) ; break ; case Instruction . REL : this . relOp ( this . getArg ( v , 0 ) ) ; break ; case Instruction . HALT : default : this . haltOp ( ) ; break ; } } while ( ! this . halt ) ; } public run ( target ? ) { if ( target ) { target . push ( ... this . execute ( ) ) ; } else { this . execute ( ) ; } return this ; } public execute ( ) { return [ ... this . iter ( ) ] ; } private addOp ( a , b , pos ) { this . tape . set ( pos , a + b ) ; this . cursor += 4 ; } private mulOp ( a , b , pos ) { this . tape . set ( pos , a * b ) ; this . cursor += 4 ; } private inOp ( pos ) { if ( this . inputQueue && this . inputQueue . length > 0 ) { this . tape . set ( pos , this . inputQueue . shift ( ) as number ) ; } else if ( this . inputCallback ) { this . tape . set ( pos , this . inputCallback ( ) ) ; } else { throw new Error ( 'No input' ) ; } this . cursor += 2 ; } private outOp ( pos ) { if ( pos === undefined ) { throw new Error ( 'Not valid output' + pos ) ; } if ( this . outputCallback ) { this . outputCallback ( pos ) ; } this . cursor += 2 ; return pos ; } private jitOp ( v , target ) { if ( v ) { this . cursor = target ; } else { this . cursor += 3 ; } } private jifOp ( v , target ) { if ( ! v ) { this . cursor = target ; } else { this . cursor += 3 ; } } private ltOp ( a , b , pos ) { this . tape . set ( pos , a < b ? 1 : 0 ) ; this . cursor += 4 ; } private eqOp ( a , b , pos ) { this . tape . set ( pos , a === b ? 1 : 0 ) ; this . cursor += 4 ; } private relOp ( pos ) { this . relBase += pos ; this . cursor += 2 ; } private haltOp ( ) { this . halt = true ; } }