Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync robot-simulator #784

Merged
merged 13 commits into from
Aug 3, 2024
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@
"uuid": "cc6c3b27-b719-454c-a743-7bc73bd47cf1",
"practices": [],
"prerequisites": [],
"difficulty": 3,
"difficulty": 5,
"topics": [
"oop"
]
Expand Down
11 changes: 4 additions & 7 deletions exercises/practice/robot-simulator/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ The robots have three possible movements:
- 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,
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 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.
- Say a robot starts at {7, 3} facing north.
Then running this stream of instructions should leave it at {9, 4} facing west.
36 changes: 36 additions & 0 deletions exercises/practice/robot-simulator/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Design

## Goal

This exercise trains object oriented programming in PHP.
The main aspect is data encapsulation.

A robot is inherently stateful.
This state shall be encapsulated as much as possible, but still must be accessible from the outside.
Make students think about the many ways PHP supports doing this.

## Learning objectives

- Recognize the possibility to use constructor promotion
- Recognize the getter method concept for data encapsulation
- Know what `public`, `protected` and `private` properties are
- Know how to implement getter methods, so internal data becomes
- readonly to the outside
- independent of interface contracts with the external world

## Out of scope

- Differences between `private` and `protected` properties

## Prerequisites

- We have no concept for OOP or data encapsulation, yet
- Basic understanding of OOP and data encapsulation
- Reading unit tests
- Classes, properties and methods in PHP

## Analyzer

This exercise could benefit from the following rules added to a future analyzer:

- To be defined
172 changes: 45 additions & 127 deletions exercises/practice/robot-simulator/.meta/example.php
Original file line number Diff line number Diff line change
@@ -1,132 +1,69 @@
<?php

/*
* By adding type hints and enabling strict type checking, code can become
* easier to read, self-documenting and reduce the number of potential bugs.
* By default, type declarations are non-strict, which means they will attempt
* to change the original type to match the type specified by the
* type-declaration.
*
* In other words, if you pass a string to a function requiring a float,
* it will attempt to convert the string value to a float.
*
* To enable strict mode, a single declare directive must be placed at the top
* of the file.
* This means that the strictness of typing is configured on a per-file basis.
* This directive not only affects the type declarations of parameters, but also
* a function's return type.
*
* For more info review the Concept on strict type checking in the PHP track
* <link>.
*
* To disable strict typing, comment out the directive below.
*/

declare(strict_types=1);

class Robot
class RobotSimulator
{
public const DIRECTION_NORTH = 'north';
public const DIRECTION_EAST = 'east';
public const DIRECTION_SOUTH = 'south';
public const DIRECTION_WEST = 'west';

/**
*
* @var int[]
*/
protected $position;

/**
*
* @var string
*/
protected $direction;
private const DIRECTION_NORTH = 'north';
private const DIRECTION_EAST = 'east';
private const DIRECTION_SOUTH = 'south';
private const DIRECTION_WEST = 'west';

/** @param int[] $position */
public function __construct(
private array $position,
private string $direction,
) {
}

public function __construct(array $position, string $direction)
public function instructions(string $instructions): void
{
$this->position = $position;
$this->direction = $direction;
if (!preg_match('/^[RLA]+$/', $instructions)) {
throw new InvalidArgumentException('Malformed instructions');
}

foreach (str_split($instructions) as $instruction) {
match ($instruction) {
'R' => $this->turnRight(),
'L' => $this->turnLeft(),
'A' => $this->advance(),
};
}
}

/**
* Make protected properties read-only.
* __get() is slow, but it's ok for the given task.
*
* @param string $name
* @return mixed
*/
public function __get($name)
/** @return int[] */
public function getPosition(): array
{
return property_exists(self::class, $name) ? $this->$name : null;
return $this->position;
}

/**
* Turn the Robot clockwise
* @return Robot
*/
public function turnRight(): \Robot
public function getDirection(): string
{
$this->direction = self::listDirectionsClockwize()[$this->direction];
return $this;
return $this->direction;
}

/**
* Turn the Robot counterclockwise
* @return Robot
*/
public function turnLeft(): \Robot
private function turnRight(): void
{
$this->direction = self::listDirectionsCounterClockwize()[$this->direction];
return $this;
$this->direction = $this->listDirectionsClockwize()[$this->direction];
}

/**
* Advance the Robot one step forward
* @return Robot
*/
public function advance(): \Robot
private function turnLeft(): void
{
switch ($this->direction) {
case self::DIRECTION_NORTH:
$this->position[1]++;
break;

case self::DIRECTION_EAST:
$this->position[0]++;
break;

case self::DIRECTION_SOUTH:
$this->position[1]--;
break;

case self::DIRECTION_WEST:
$this->position[0]--;
break;
}
return $this;
$this->direction = $this->listDirectionsCounterClockwize()[$this->direction];
}

/**
* Move the Robot according to instructions: R = Turn Right, L = Turn Left and A = Advance
*/
public function instructions($instructions)
private function advance(): void
{
if (!preg_match('/^[LAR]+$/', $instructions)) {
throw new InvalidArgumentException('Malformed instructions');
}

foreach ($this->mapInsructionsToActions($instructions) as $action) {
$this->$action();
}
return $this;
match ($this->direction) {
self::DIRECTION_NORTH => $this->position[1]++,
self::DIRECTION_EAST => $this->position[0]++,
self::DIRECTION_SOUTH => $this->position[1]--,
self::DIRECTION_WEST => $this->position[0]--,
};
}

/**
* List all possible clockwise turn combinations
* @return array
*/
public static function listDirectionsClockwize(): array
/** @return Array<string, string> */
private function listDirectionsClockwize(): array
{
return [
self::DIRECTION_NORTH => self::DIRECTION_EAST,
Expand All @@ -136,28 +73,9 @@ public static function listDirectionsClockwize(): array
];
}

/**
* List all possible counterclockwise turn combinations
* @return array
*/
public static function listDirectionsCounterClockwize(): array
{
return array_flip(self::listDirectionsClockwize());
}

/**
* Translate instructions string to actions
* @param string $stringInstructions
* @return string[]
*/
protected function mapInsructionsToActions($stringInstructions): array
/** @return Array<string, string> */
private function listDirectionsCounterClockwize(): array
{
return array_map(function ($x) {
return [
'L' => 'turnLeft',
'R' => 'turnRight',
'A' => 'advance',
][$x];
}, str_split($stringInstructions));
return array_flip($this->listDirectionsClockwize());
}
}
30 changes: 10 additions & 20 deletions exercises/practice/robot-simulator/RobotSimulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,27 @@

declare(strict_types=1);

class Robot
class RobotSimulator
{
/**
*
* @var int[]
*/
protected $position;

/**
*
* @var string
*/
protected $direction;

/** @param int[] $position */
public function __construct(array $position, string $direction)
{
throw new \BadMethodCallException("Implement the __construct method");
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
}

public function turnRight(): self
public function instructions(string $instructions): void
{
throw new \BadMethodCallException("Implement the turnRight method");
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
}

public function turnLeft(): self
/** @return int[] */
public function getPosition(): array
{
throw new \BadMethodCallException("Implement the turnLeft method");
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
}

public function advance(): self
public function getDirection(): string
{
throw new \BadMethodCallException("Implement the advance method");
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
}
}
Loading