No description, website, or topics provided.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
project
src
target
README.md
build.sbt

README.md

The Graffiti

Task

You're given the task of writing a simple console version of a drawing program. The functionality of the program is quite limited but this might change in the future. The program should work as follows:

  1. create a new canvas.
  2. start drawing on the canvas by issuing various commands.
  3. quit. The program should support the following commands:

C w h Should create a new canvas of width w and height h.

L x1 y1 x2 y2 Should create a new line from (x1,y1) to (x2,y2). Currently only horizontal or vertical lines are supported. Horizontal and vertical lines will be drawn using the 'x' character.

R x1 y1 x2 y2 Should create a new rectangle, whose upper left corner is (x1,y1) and lower right corner is (x2,y2). Horizontal and vertical lines will be drawn using the 'x' character.

B x y c Should fill the entire area connected to (x,y) with colour 'c'. The behaviour of this is the same as that of the "bucket fill" tool in paint programs.

Q Should quit the program.

Installation

$ git clone https://github.com/rcerka01/ConsoleVersionOfDrawingProgram.git
$ cd ConsoleVersionOfDrawingProgram
$ sbt run

Test

$ sbt test

Description

To make easy to extend the functionality and organise clear code the application is written by following an MVC pattern. It is divided into model - canvas description, controller - redirecting to canvas services (create canvas, add line, etc.), view - output. There is added router, Input and Main object which ignites the application. The validation is called as a service at the input level and at the controller, specific for each controller command. CanvasServices are doing actual manipulations with the canvas.

The entry point into the application is through Main object in Main.scala file. It only activates Input object 'run' function. The 'run' is responsible for reading the command (what ever comes from console) and split it to an array of String by using space " " as a separator.

After input, if command is 'Q' application terminates (it is not redirected to another 'run', if anything else, it will go trough the 1st validation process. There are two validation levels, first in the input, to find if command is actually a command, and second level at the controller, specific to each controller command.

Command from input is recognised as a command if it starts with rather C, L, R or B who are followed by an empty space. There are no other error checks at the first validation process. Both validation levels are using Boolean functions placed at ValidationServices object under 'services' package. If command validation fails at input, the 'run' function is called again. If pass, command is passed further to the router.

The Router is in Router.scala file. It has only one function 'runCommand'. According on first command letter (which already pass the validation) router redirects to the appropriate controller.

The controller runs a validation specific to the each command. If it fails - controller is redirecting to input, if pass - a specific canvas service is called. Each canvas service is returning the Canvas object. Canvas object is passed further to Output object and printed trough 'printCanvas' function. Canvas object has a method 'exportToPrint' which is returning canvas as a 2D array for 'prinCanvas' function to print it out.

Second level validation.

to create Canvas:

  • right amount of parameters
  • are they valid to Int
  • are they positive

to add line:

  • is canvas created
  • right amount of parameters
  • are parameters valid to int
  • is line horizontal or vertical
  • is line inside canvas

to add rectangle

  • is canvas created
  • right amount of parameters
  • are they valid to Int
  • is top left corner actually on top left
  • is bottom right corner actually on bottom right
  • is rectangle inside canvas

to add fill

  • is canvas created
  • right amount of parameters
  • are parameters valid to int
  • is fill in canvas
  • is 4th parameter just one symbol

Each canvas service is returning a new Canvas object which is generated from its previous state (or null if command is create canvas). The canvas current state is stored in canvas private variable (var) inside CanvasService object. This is arguable decision. It could be very handy to extend application to use many layers. Variable canvas could become list of canvas, therefore keeping all drawings in the history. It would be easy to implement back and forward button, etc. And each change would be represented as separate canvas in the list.

Canvas class is the heart of the application. It is constructed with x, y as a canvas size, sequence of lines, rectangles and fills. When 'addLine', 'addRectangle' or 'addFill' services are called, the new canvas object is generated by using previous object parameters, plus added a new element (e.g. line) to the relevant sequence. In the Canvas constructor the 'canvas' private array of [x, y] is defined. Its first / last row and column are filled with border symbol, the rest with an empty space. Borders are helping to normalise coordinates. They become equal to array indexes (no more need for x-1, y-1).

After the canvas is defined, the constructor is filling it with lines, rectangles and fills. function addLine - single loop to add a line of 'x' in canvas array. It is different for horizontal and vertical lines where each has a diversity - normal or if the last coordinate is actually in front (it is not qualified as an error). function addRectangle - draws two vertical and two horizontal lines (corners are drawn 2x). function addFill - recursive function which reads a symbol from array coordinates, then crawls in four directions to look for the same symbol and replace it with the symbol passed to function.

Tests

The canvas, services and command validation is tested thoroughly. The Input object, controller and router are just passing further the commands. To test them could be recognised as an unnecessary boilerplate code.

I hope the code is clearer than this description :)