Advent of Code 2024 day 13, RPG edition

This is the thirteenth day of Advent of Code, let’s solve today’s puzzles with RPG.

The puzzle as well as the input data are available here.

The code is available here.

Part 1

We are provided with a (stream) file containing configurations for a claw machine.

The claw machine has 2 buttons: A and B. Each button moves a given quantity (defined in the input) on X and Y axis. To win, we need to press A and B buttons a given number of times each to reach a given X and Y position (defined in the input).

It’s not possible to win all configurations.

Pressing A button costs 3 tokens, pressing B button costs 1 token.

The goal is to calculate how many token are need to win all winnable configurations.

The puzzle description seem to encourage a brute force method but let’s just math it out!

A solution can be represented as 2 equations with two unknown: Ax + By = C Dx + Ey = F

Where:

  • x is the number of A button press
  • y is the number of B button press
  • A is the X offset of A button
  • B is the X offset of B button
  • C is the target X coordinate
  • D is the Y offset of A button
  • E is the Y offset of B button
  • F is the target Y coordinate

If you don’t feel like solving those equations, you can ask Wolfram.

The solution is: x=(CE-BF)/(AE-BD) and y=(CD-AF)/(BD-AE)

We simply calculate the value of x and y. To be a solution, x and y must be positive (or null) whole number. To avoid a false solution because of a rounding error, we verify the x and y values actually work.

As usual, we read the input file with QSYS2.IFS_READ_UTF8 table function.

Here is the RPG code for part 1:

**free
ctl-opt dftactgrp(*no);  // We're using procedure so we can't be in the default activation group

dcl-pi *n;
  input char(50);
end-pi;

dcl-s fileName varchar(50);

dcl-s data varchar(255);
dcl-s val varchar(10);

dcl-s result int(20) inz(0);

dcl-s ax int(5);
dcl-s ay int(5);
dcl-s bx int(5);
dcl-s by int(5);
dcl-s x int(20);
dcl-s y int(20);
dcl-s a packed(20:5);
dcl-s b packed(20:5);

fileName = %trim(input);

// Read the input data from the IFS
exec sql declare c1 cursor for select line from table(qsys2.ifs_read_utf8(path_name => :fileName));
exec sql open c1;

// Read first line
exec sql fetch c1 into :data;

dow sqlcode = 0;
  // extract ax
  exec sql set :val = regexp_substr(:data, 'Button A: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 1);
  ax = %int(val);
  // extract ay
  exec sql set :val = regexp_substr(:data, 'Button A: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 2);
  ay = %int(val);

  // Read second line
  exec sql fetch c1 into :data;
  // extract bx
  exec sql set :val = regexp_substr(:data, 'Button B: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 1);
  bx = %int(val);
  // extract by
  exec sql set :val = regexp_substr(:data, 'Button B: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 2);
  by = %int(val);
 
  // Read third line
  exec sql fetch c1 into :data;
  // extract x
  exec sql set :val = regexp_substr(:data, 'Prize: X=(\d+), Y=(\d+)', 1, 1, 'c', 1);
  x = %int(val);
  // extract y
  exec sql set :val = regexp_substr(:data, 'Prize: X=(\d+), Y=(\d+)', 1, 1, 'c', 2);
  y = %int(val);

  a = (x*by-bx*y)/(ax*by-bx*ay);
  b = (x*ay-ax*y)/(-ax*by+bx*ay);

  if a>=0 and b>=0 and %int(a) = a and %int(b) = b;
    if a*ax+b*bx = x and a*ay+b*by = y;
      result += %int(a)*3 + %int(B);
    endif;
  endif;
 
  // Read next 2 lines
  exec sql fetch c1 into :data;
  exec sql fetch c1 into :data;
enddo;

exec sql close c1;

snd-msg *info 'Result: ' + %char(result) %target(*pgmbdy:1); // Send message with answer

*inlr = *on;
return;

Part 2

In part 2, we add a big number to the target coordinates.

If had used a brute force method in part 1, we would be in trouble. Since we went the math way, we just have to change the target coordinates.

**free
ctl-opt dftactgrp(*no);  // We're using procedure so we can't be in the default activation group

dcl-pi *n;
  input char(50);
end-pi;

dcl-s fileName varchar(50);

dcl-s data varchar(255);
dcl-s val varchar(10);

dcl-s result int(20) inz(0);

dcl-s ax int(5);
dcl-s ay int(5);
dcl-s bx int(5);
dcl-s by int(5);
dcl-s x int(20);
dcl-s y int(20);
dcl-s a packed(20:5);
dcl-s b packed(20:5);

fileName = %trim(input);

// Read the input data from the IFS
exec sql declare c1 cursor for select line from table(qsys2.ifs_read_utf8(path_name => :fileName));
exec sql open c1;

// Read first line
exec sql fetch c1 into :data;

dow sqlcode = 0;
  // extract ax
  exec sql set :val = regexp_substr(:data, 'Button A: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 1);
  ax = %int(val);
  // extract ay
  exec sql set :val = regexp_substr(:data, 'Button A: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 2);
  ay = %int(val);

  // Read second line
  exec sql fetch c1 into :data;
  // extract bx
  exec sql set :val = regexp_substr(:data, 'Button B: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 1);
  bx = %int(val);
  // extract by
  exec sql set :val = regexp_substr(:data, 'Button B: X\+(\d+), Y\+(\d+)', 1, 1, 'c', 2);
  by = %int(val);
 
  // Read third line
  exec sql fetch c1 into :data;
  // extract x
  exec sql set :val = regexp_substr(:data, 'Prize: X=(\d+), Y=(\d+)', 1, 1, 'c', 1);
  x = %int(val)+10000000000000;
  // extract y
  exec sql set :val = regexp_substr(:data, 'Prize: X=(\d+), Y=(\d+)', 1, 1, 'c', 2);
  y = %int(val)+10000000000000;

  a = (x*by-bx*y)/(ax*by-bx*ay);
  b = (x*ay-ax*y)/(-ax*by+bx*ay);

  if a>=0 and b>=0 and %int(a) = a and %int(b) = b;
    if a*ax+b*bx = x and a*ay+b*by = y;
      result += %int(a)*3 + %int(B);
    endif;
  endif;
 
  // Read next 2 lines
  exec sql fetch c1 into :data;
  exec sql fetch c1 into :data;
enddo;

exec sql close c1;

snd-msg *info 'Result: ' + %char(result) %target(*pgmbdy:1); // Send message with answer

*inlr = *on;
return;