The main idea of this project is to prove that complicated Machine Learning methods and tools not always allow to reach a desired goal.
The first part of this project is to build an application, which outputs a controllable snake game model (in different words a well-known snake game).
The second part is to connect an artificial player which will be simulated by a simple NN or a simple mathematical model.
And the first part of the project was finished in terms of the small university project, the main idea of the pet project is to make acquaintance with some topics of Software engineering.
Travis-CI:
AppVeyor:
CodeCov:
CodeClimate:
Sonarcloud:
To create UML diagrams I used a PlantUML plugin in PyCharm.
Besides the badges at the beginning of the page, you can find additional information about used metrics on the following pages:
- sonarcloud.io page
- codeclimate page
- codecov.io page
During development were used Clean Code Development principles and PEP Conventions.
- Method Names and Instance Variables:
Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.
def __get_pos_on_screen(self, snake_pos): return snake_pos * self.scale_coef + self.scale_coef // 2
- Class Names:
Class names should normally use the CapWords convention.
class Playground(object): ... class Food: ... class Direction: ...
- Function rules: Small, Do one thing, Use descriptive names:
def get_body(self): return self.__body def get_body_list(self): return [segment.get_pos() for segment in self.get_body()] def get_speed(self): return self.__speed.dir def get_position(self): return self.__position.pos def get_allowed_space(self): return self.__pg.rows, self.__pg.rows def get_seen_food_pos(self): return self.__food.get_pos()
- Source code structure: Similar function should be close
def __is_injuring_itself(self, new_position): segments = [segment.pos for segment in self.__body] if new_position.get_pos() in segments: return True return False def __is_colliding_wall(self, new_position): if new_position.get_pos() in self.__borders: return True return False def get_body(self): return self.__body def get_body_list(self): return [segment.get_pos() for segment in self.get_body()] def get_speed(self): return self.__speed.dir def get_position(self): return self.__position.pos def get_allowed_space(self): return self.__pg.rows, self.__pg.rows def get_seen_food_pos(self): return self.__food.get_pos() def turn(self, new_dir): new_speed = self.__speed + Direction(direc=new_dir) if any(new_speed) != 0: self.__speed = Direction(direc=new_dir) def move(self): new_position = self.__position + Position(self.__speed.get_dir()) if self.__is_injuring_itself(new_position): return Snake.self_collision if self.__is_colliding_wall(new_position): return Snake.wall_collision self.__position = new_position self.__move_body()
- Maximum Line Length:
Limit all lines to a maximum of 79 characters.
Interesting fact: Python does not have namespaces like C++ or Java, so I use Constants logically attached to the class (
pygame.draw.rect(self.screen, Window.GREEN, (x_pos - self.scale_coef // 2, y_pos - self.scale_coef // 2, self.scale_coef, self.scale_coef))
Window.GREEN
). PEP keeps silence in this case.
The easiest way to use Build Management system with Python is PyGradle. It does the following things:
- installs environment, dependencies
- launches tests
- builds python wheel
- generates docs (html and xml)
In current project:
- build.gradle file has instructions to build.
- gradle_report.txt file is an output example.
To have possibility to launch gradle even without IDE I have made a simple bash script, that does this.
Although, the project has GUI, it was not covered by tests by understandable reasons. Only the snake model was covered. test_snake_model.py
Unfortunately my project does not have delivery part at least standard one like PyPI or some Python-based website. So Let's assume I deliver my project just on github with all green/yellow values of the badges. So that all of them were green or at least yellow for such badges as test coverage.
First of all, Travis-CI is responsible for Linux and AppVeyer for Windows.
Travis-CI service after the unit tests sends reports to CodeClimate.com and codecov.io. Details can be found in travis.yml file for Travis and in appveyor.yml, tox.ini for AppVeyor.
Gradle report usually looks like on the picture below.
I have used PyCharm as my IDE.
I used such plugins as:
- IdeaVim (to fasten development)
- PlantUML (to draw UML diagrams)
- Nyan Progress Bar (because it is fun)
About my knowledge of vim, besides simple things from vimtutor, I can
write a complex Macros (with usage increment and decrement), launch
shell utils regarding to the text in the editor (for example
to show only the 10th column of a big csv file I can do this
:% !colomn -c 10 -t -c,
), operate with buffers and etc.
Also I used some shortcuts in the IDE:
- Run script (⌘R) (there is a conflict with vim, so it can be different from default set up)
- Debug script (⌘D) (there is a conflict with vim, so it can be different from default set up)
- Commit (⌘K)
- Refactor rename (⇧F6)
- Preferences.. (⌘,)
Domain Specific Language was created to control the snake. You can see usage example below.
Gif looks worse than an original video, so below you can find the original video and even an old video (one of the first attempts).
Full version of the video is available here and here.
As you can see there is used vim navigation to control the snake
, because I am a fan of vim. Also, there was provided opportunity to
enter commands in lots of different ways. For example, instead of
to push ENTER
11 times to reach the food a player can just enter 11k
.
In addition a player can enter series commands for instance
5h k l k 23h
.
-
Final data structures
There is tuple in my class Direction.
class Direction: up = (-1, 0) dn = (1, 0) lt = (0, -1) rt = (0, 1)
-
Side effect free functions
An instance:
def command_handler(command): if command in turns(): dir = turns()[command] snake.turn(dir) elif command == 'q': return False return True
-
The use of higher order functions
Below you can see a few artifical made examples: arg in fuction number is a tuple that consist of a callback and int variable respectively.
def number(the_number, arg): return arg[0](the_number, arg[1]) if arg else the_number def zero (arg = None): return number(0, arg) def one (arg = None): return number(1, arg) def two (arg = None): return number(2, arg) def three(arg = None): return number(3, arg) def four (arg = None): return number(4, arg) def five (arg = None): return number(5, arg) def six (arg = None): return number(6, arg) def seven(arg = None): return number(7, arg) def eight(arg = None): return number(8, arg) def nine (arg = None): return number(9, arg) def plus (value): return int.__add__, value def minus (value): return int.__sub__, value def times (value): return int.__mul__, value def divided_by(value): return int.__floordiv__, value assert seven(times(five())) == 35 assert four(plus(nine())) == 13 assert eight(minus(three())) == 5 assert six(divided_by(two())) == 3
-
Functions as parameters and return values/anonymous functions
When I was at school I often had to solve equastions like this: $ (a_1x^2 + a_2^x + a_3) * x^2 + (a_4x + a_5) * x + a_6 $
def quad(a, b, c): return lambda x: (a(x) if callable(a) else a)*(x ** 2) + \ (b(x) if callable(b) else b) * x + \ (a(x) if callable(c) else c) assert quad(0, 0, 3)(0) == 3 assert quad(quad(1, 0, 0), quad(0, 2, 0), 3)(0) == 3
-
Use Closures
def turns(): TURNS = {'h': "lt", 'j': "dn", 'k': "up", 'l': "rt"} def nested_function(): return TURNS return nested_function()