Avatar of 4d47
1
0
Genius
0
0

4d47's solution

to Robot Simulator in the PHP Track

1
0
Genius
0
0
Instructions
Test suite
Solution

Write a robot simulator.

A robot factory's test facility needs a program to verify robot movements.

The robots have three possible movements:

  • turn right
  • turn left
  • advance

Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east.

The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing.

  • The letter-string "RAALAL" means:
    • Turn right
    • Advance twice
    • Turn left
    • Advance once
    • Turn left yet again
  • Say a robot starts at {7, 3} facing north. Then running this stream of instructions should leave it at {9, 4} facing west.

Running the tests

  1. Go to the root of your PHP exercise directory, which is <EXERCISM_WORKSPACE>/php. To find the Exercism workspace run

     % exercism debug | grep Workspace
    
  2. Get PHPUnit if you don't have it already.

     % wget --no-check-certificate https://phar.phpunit.de/phpunit.phar
     % chmod +x phpunit.phar
    
  3. Execute the tests:

     % ./phpunit.phar robot-simulator/robot-simulator_test.php
    

Source

Inspired by an interview question at a famous company.

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

robot-simulator_test.php

<?php

include_once 'robot-simulator.php';

class RobotSimulatorTest extends PHPUnit\Framework\TestCase
{


    /**
     * A robot is created with a position and a direction
     */
    public function testCreate()
    {
        // Robots are created with a position and direction
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_NORTH, $robot->direction);

        // Negative positions are allowed
        $robot = new Robot([-1, -1], Robot::DIRECTION_SOUTH);
        $this->assertEquals([-1, -1], $robot->position);
        $this->assertEquals(Robot::DIRECTION_SOUTH, $robot->direction);
    }


    /**
     * Rotate the robot's direction 90 degrees clockwise
     */
    public function testTurnRight()
    {
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);

        // Change the direction from north to east
        $robot->turnRight();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_EAST, $robot->direction);

        // Change the direction from east to south
        $robot->turnRight();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_SOUTH, $robot->direction);

        // Change the direction from south to west
        $robot->turnRight();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_WEST, $robot->direction);

        // Change the direction from west to north
        $robot->turnRight();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_NORTH, $robot->direction);
    }

    /**
     * Rotate the robot's direction 90 degrees counterclockwise
     */
    public function testTurnLeft()
    {
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);

        // Change the direction from north to west
        $robot->turnLeft();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_WEST, $robot->direction);

        // Change the direction from west to south
        $robot->turnLeft();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_SOUTH, $robot->direction);

        // Change the direction from south to east
        $robot->turnLeft();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_EAST, $robot->direction);

        // Change the direction from east to north
        $robot->turnLeft();
        $this->assertEquals([0, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_NORTH, $robot->direction);
    }


    /**
     * Move the robot forward 1 space in the direction it is pointing
     */
    public function testAdvance()
    {
        // Increases the y coordinate by one when facing north
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);
        $robot->advance();
        $this->assertEquals([0, 1], $robot->position);
        $this->assertEquals(Robot::DIRECTION_NORTH, $robot->direction);

        // Decreases the y coordinate by one when facing south
        $robot = new Robot([0, 0], Robot::DIRECTION_SOUTH);
        $robot->advance();
        $this->assertEquals([0, -1], $robot->position);
        $this->assertEquals(Robot::DIRECTION_SOUTH, $robot->direction);

        // Increases the x coordinate by one when facing east
        $robot = new Robot([0, 0], Robot::DIRECTION_EAST);
        $robot->advance();
        $this->assertEquals([1, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_EAST, $robot->direction);

        // Decreases the x coordinate by one when facing west
        $robot = new Robot([0, 0], Robot::DIRECTION_WEST);
        $robot->advance();
        $this->assertEquals([-1, 0], $robot->position);
        $this->assertEquals(Robot::DIRECTION_WEST, $robot->direction);
    }


    /**
     * Where R = Turn Right, L = Turn Left and A = Advance,
     * the robot can follow a series of instructions
     * and end up with the correct position and direction
     */
    public function testInstructions()
    {
        // Instructions to move west and north
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);
        $robot->instructions('LAAARALA');
        $this->assertEquals([-4, 1], $robot->position);
        $this->assertEquals(Robot::DIRECTION_WEST, $robot->direction);

        // Instructions to move west and south
        $robot = new Robot([2, -7], Robot::DIRECTION_EAST);
        $robot->instructions('RRAAAAALA');
        $this->assertEquals([-3, -8], $robot->position);
        $this->assertEquals(Robot::DIRECTION_SOUTH, $robot->direction);

        // Instructions to move east and north
        $robot = new Robot([8, 4], Robot::DIRECTION_SOUTH);
        $robot->instructions('LAAARRRALLLL');
        $this->assertEquals([11, 5], $robot->position);
        $this->assertEquals(Robot::DIRECTION_NORTH, $robot->direction);
    }


    /**
     * @expectedException InvalidArgumentException
     */
    public function testMalformedInstructions()
    {
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);
        $robot->instructions('LARX');
    }


    /**
     * Optional instructions chaining
     */
    public function testInstructionsChaining()
    {
        $robot = new Robot([0, 0], Robot::DIRECTION_NORTH);
        $robot->turnLeft()
            ->advance()
            ->advance()
            ->advance()
            ->turnRight()
            ->advance()
            ->turnLeft()
            ->advance();
        $this->assertEquals([-4, 1], $robot->position);
        $this->assertEquals(Robot::DIRECTION_WEST, $robot->direction);
    }
}
<?php

class Robot {
    const
        DIRECTION_NORTH = [  0,  1 ],
        DIRECTION_EAST  = [  1,  0 ],
        DIRECTION_SOUTH = [  0, -1 ],
        DIRECTION_WEST  = [ -1,  0 ];

    public
        $position,
        $direction;

    public function __construct(array $position, array $direction) {
        assert(count($position) == 2);
        $this->position = $position;
        $this->direction = $direction;
    }

    public function turnRight(): Robot {
        [$x, $y] = $this->direction;
        $this->direction = [$y, -$x];
        return $this;
    }

    public function turnLeft(): Robot {
        [$x, $y] = $this->direction;
        $this->direction = [-$y, $x];
        return $this;
    }

    public function advance(): Robot {
        $this->position[0] += $this->direction[0];
        $this->position[1] += $this->direction[1];
        return $this;
    }

    public function instructions(string $steps): Robot {
        if (!preg_match('/^[LRA]*$/', $steps)) {
            throw new InvalidArgumentException('Instruction set must only contains L, R or A.');
        }
        for ($i = 0; $i < strlen($steps); $i++) {
            switch ($steps[$i]) {
            case 'L': $this->turnLeft(); break;
            case 'R': $this->turnRight(); break;
            case 'A': $this->advance(); break;
            }
        }
        return $this;
    }
}

What can you learn from this solution?

A huge amount can be learnt from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that I could read more about to develop my understanding?