Skip to content

LightningDev/toy-robot-challenge

Repository files navigation

Toy Robot Challenge

This project is an implementation of the Toy Robot simulation, allowing a toy robot to roam around a 5x5 or nxn (where n is any integer number) grid and execute a series of commands.

Table of Contents

  1. Introduction
  2. Project Structure
  3. Implementation
  4. Getting Started
  5. CLI Overview
  6. Extra Features
  7. Possible Improvements

Introduction

  • The application is a simulation of a toy robot moving on a square tabletop, of dimensions 5 units x 5 units.
  • There are no other obstructions on the table surface.
  • The robot is free to roam around the surface of the table, but must be prevented from falling to destruction. Any movement that would result in the robot falling from the table must be prevented, however further valid movement commands must still be allowed.

Create an application that can read in commands of the following form:

PLACE X,Y,F
MOVE
LEFT
RIGHT
REPORT
MOVE
REPORT
  • PLACE will put the toy robot on the table in position X,Y and facing NORTH, SOUTH, EAST or WEST.
  • The origin (0,0) can be considered to be the SOUTH WEST most corner.
  • The first valid command to the robot is a PLACE command, after that, any sequence of commands may be issued, in any order, including another PLACE command. The application should discard all commands in the sequence until a valid PLACE command has been executed.
  • MOVE will move the toy robot one unit forward in the direction it is currently facing.
  • LEFT and RIGHT will rotate the robot 90 degrees in the specified direction without changing the position of the robot.
  • REPORT will announce the X,Y and orientation of the robot.
  • A robot that is not on the table can choose to ignore the MOVE, LEFT, RIGHT and REPORT commands.
  • The application must not exit after the first REPORT command, i.e. many REPORT commands can be received per session.
  • It is up to you how you exit the application (e.g. exit command, Ctrl-C etc.)
  • Provide test data to exercise the application.

Constraints

The toy robot must not fall off the table during movement. This also includes the initial placement of the toy robot. Any move that would cause the robot to fall must be ignored.

Example Input and Output:

PLACE 0,0,NORTH
MOVE
REPORT
Output: 0,1,NORTH
PLACE 0,0,NORTH
LEFT
REPORT
Output: 0,0,WEST
PLACE 1,2,EAST
MOVE
MOVE
LEFT
MOVE
REPORT
Output: 3,3,NORTH

Project Structure

.
├── .github
│   ├── workflows     // simple github action for ci
├── build             // dockerfile
├── cmd               // command list of CLI
├── config            // command configuration
├── internal          // internal package for robot app
│   ├── errors        // custom errors handler
│   ├── generator     // generate command from template
│   └── parser        // parse command from user input
├── pkg               // package folder of robot app
│   ├── command       // command logic
│   ├── position      // position and direction module
│   ├── robot         // robot module
│   └── table         // table module
└── test              // external test data and helper file

Implementation

The project not only addresses the foundational requirements of the toy robot challenge but also introduces several enhancements to improve the user experience and the system's extensibility:

  • Implementation of all basic commands.
  • Allow permissive input and not forcing capital command to improve user experience.
  • Provision of user-friendly error messages, coupled with a debug mode for detailed error insights.
  • Capability to introduce additional commands on-the-fly.
  • Flexibility to dynamically set the table size.
  • Can add more CLI command so it can be used for other purposes.

Questions

Given that commands can be case-sensitive and considering the intended audience for this app, it's beneficial to offer a user-friendly interface with permissive input to foster a pleasant experience.

However, every decision has its trade-offs. From a developer's perspective, it's essential to ensure that all case combinations are comprehended and processed correctly.

Overall, prioritizing a superior user experience aligns with the primary objective for this application.

Design

When beginning the design of a solution, it's crucial to ensure the flexibility and maintainability of the code. While perfection might not be achieved on the first try, adhering to standard coding practices can aid in making improvements more seamlessly.

Here are some key takeaways from the implementation of this project:

  • A Robot is an actor with properties that can be influenced by external actions.
  • These external actions, such as commands, should be designed flexibly. They should be implementable without altering the core Robot codebase.
  • The Robot doesn't need to understand the intricacies of an action; its primary role is to execute it.
  • Drawing a parallel, consider a car. Various cars have distinct designs, but as a driver, your main goal is to press the pedal to set it in motion, without needing a deep understanding of its mechanics. :)
  • The table also has a public property for its size. This allows it to be initialized on-the-fly via the CLI, enabling games to start with custom sizes rather than just the hardcoded 5 x 5 dimension.
  • While the app will skip any invalid commands as per the requirements, it should still provide a user-friendly message to inform the user about the situation.

Getting Started

Prerequisites

Running the Project

The project can be executed either locally or using Docker.

Local

  1. Make sure you have all the prerequisites installed.
  2. Intall all go packages:
go mod download
go mod verify
  1. Run direct with go command or via a build
go run . play

or

go build -o toy-robot
./toy-robot play

Docker

  1. Build image
docker build -f ./build/Dockerfile -t toyrobot-app .
  1. Run container from image
docker run -it --rm toyrobot-app play

Makefile

I also created a Makefile so it can be simple to run via make command For Docker:

make build-docker
make run-docker

Run directly on your machine:

make run

Running the tests

go test ./... -v

Run by Makefile

make test

The test folder provides a JSON file that can be used to set up extensive test cases and their expected outputs.

You can add additional test cases using the following format to use in cmd/cmd_test.go. For example:

{
  "commands": ["PLACE 5,5,NORTH", "REPORT"],
  "output": [
    "Command 'PLACE': invalid position",
    "Command 'REPORT': please place the robot first"
  ]
}

CLI Overview

The CLI currently has 2 main commands:

  • play: starts the game
  • add command <command_name>: adds a new command to the app and generates a code template so you can begin development.

When starting with play, you can include a few extra flags:

  • -f or --file: specify a text file containing commands
  • --width: width of the table
  • --height: height of the table
  • -d: displays error logs in debug mode

Extra Features

Here are some extra features we have and how to run them.

Running from file

Text file should contain a single command per line. For example:

PLACE 1,1,SOUTH
MOVE
REPORT

You can place all the commands in a text file and execute them directly without manual typing. Use the following command:

go run . play -f <your_file_location>

The project root also contains a sample command file that you can test:

go run . play -f ./sample_command.txt

Running with different table size

You have the option to play the game on a larger board. Use the following command to specify the board's dimensions:

go run . play --width 10 --height 10

These flags can also be combined with the command to run from a file:

go run . play -f ./sample_command.txt --width 10 --height 10

Adding a new command template

if you wish to develop another command, the CLI offers an option to generate a template file for a new command. This allows you to easily insert your own logic.

go run . add command <your_command_name>

For example:

go run . add command jump

After running this command, it will generate two new files:

  • pkg/command/jump.go: jump command logic template
package command

import (
	"github.com/LightningDev/toy-robot-challenge/pkg/robot"
  "github.com/LightningDev/toy-robot-challenge/pkg/table"
)

type JumpCommand struct {
	Name string
}

func NewJumpCommand(args []string) (robot.Command, error) {
	return JumpCommand{
		Name: "JUMP",
	}, nil
}

func (c JumpCommand) Execute(r *robot.Robot, t table.Table) error {
	// TODO: Implement JumpCommand Logic Here
	return nil
}

func (c JumpCommand) GetName() string {
	return c.Name
}
  • pkg/command/jump_test.go: test template for jump command

Furthermore, it updates pkg/command/command.go and config/command.json to register your new command. These files can be understood as the source of truth, allowing us to check the list of available commands in the app.

pkg/command/command.go

var CommandList = map[string]func([]string) (robot.Command, error){
	"PLACE": NewPlaceCommand,
	"REPORT": NewReportCommand,
	"MOVE": NewMoveCommand,
	"LEFT": NewLeftCommand,
	"RIGHT": NewRightCommand,
	"JUMP": NewJumpCommand,
}

config/command.json

{
  "Commands": [
    "PLACE",
    "REPORT",
    "MOVE",
    "LEFT",
    "RIGHT",
    "JUMP"
  ]
}

Afterwards, you can begin implementing your own command logic within the Execute function, which is derived from the Command interface in robot.

Running with debug

By default, the app displays user-friendly error messages. However, by using the -d flag when running the command, it will also print out both the message and stack trace, enhancing the debugging experience.

go run . play -d

Path finding

The robot are able to attack the specific location with command ATTACK. It will output all the step that robot need to go to reach the location. If the location is invalid due to obtacle or out of the table, it will print invalid position or if there are no path it will print no path found to attack.

To test the command so you can get the journey of the robot to the target.

go run . play --width 10 --height 10 -f ./path_finder.txt

This will simulate the journey robot can go to position you want.

The idea behind the algorithm is A* Search algorithm to find the heuristic cost to the position you want, skip it if it's an obstacle, otherwise move on to keep calculate til you reach the target.

Possible Improvements

The generator, as it stands, can only generate an extra command. However, it should be improved to generate more than just commands, such as additional objects on the table.

The errors package could introduce more types of errors, not just ValidationError. Given the current size of the project, it's not necessary to introduce more types of errors, but it should be considered in the future.