DEV Community

loading...

Solving the Cretan maze

Alex Bender
there is no doubt in my mind that interfaces could be better.
・4 min read

So let's play the game

Recently Leonora Der published great piece of work: Solve the Cretan maze.
That challenge is really interesting because It doesn't require you to write a lot of code, but is still interesting to solve and think about it. Additional thanks for the design: simple and great.
So I've decided to implement basic interface for that game, of course written in Python.
My first intention was to write something like that:

...
class MazeRunner():

    def __init__(self, username, password):
        # For BasicHTTPAuth
        self.username = username
        self.password = password

    def create_maze(self):
        """POST - /api/mazes

        Will let you start the maze
        """

    def start_loop(self):
        ...
...
Enter fullscreen mode Exit fullscreen mode

And I even did that. Wrote a dozen methods, mostly non implemented yet but they are just stubs, you know. It covered all API provided. A few days later I've started to dig around pdb, then its parent bdb, and then back to pdb. I've noticed something interesting: here is a piece of source code for the Pdb class, file Lib/pdb.py

...

line_prefix = '\n-> '   # Probably a better default

class Pdb(bdb.Bdb, cmd.Cmd):

    _previous_sigint_handler = None

...
Enter fullscreen mode Exit fullscreen mode

wait, what was that? That second parent class. It is cmd. So as usual, Python standard lib has solved a big part of my problems. I started to read docs on this module: cmd. It appears that I've been already using this interface for more than half of decade without even noticing that: Python debuggers like pdb, ipdb, pudb.. this common REPL appears everywhere.

>>> import pdb
>>> pdb.set_trace()
--Return--
> <stdin>(1)<module>()->None
(Pdb) ?

Documented commands (type help <topic>):
========================================
EOF    bt         cont      enable  jump  pp       run      unt   
a      c          continue  exit    l     q        s        until 
alias  cl         d         h       list  quit     step     up    
args   clear      debug     help    n     r        tbreak   w     
b      commands   disable   ignore  next  restart  u        whatis
break  condition  down      j       p     return   unalias  where 

Miscellaneous help topics:
==========================
exec  pdb

Undocumented commands:
======================
retval  rv

(Pdb) 
Enter fullscreen mode Exit fullscreen mode

Let's write something useful

What do we want to solve (what do we need?)

  • Implement all API calls
  • Document them all
  • Make it clear for the user what to do (document it again and provide help)

For implementing cmd interface we have to know something:

  • the main function is cmdloop.
From the documentation:

Cmd.cmdloop(intro=None)

Repeatedly issue a prompt, accept input,
parse an initial prefix off the received input,
and dispatch to action methods, passing them the
remainder of the line as argument.
Enter fullscreen mode Exit fullscreen mode
  • command handlers are methods called do_<command-name>
An interpreter instance will recognize a command name foo if and only
if it has a method do_foo()
Enter fullscreen mode Exit fullscreen mode
  • for providing help for this command we have to implement help_<command-name> method
  • standard do_help method calls all help_* methods.
  • there are pre- and post- handlers for commands: precmd and postcmd methods.

Cmd.precmd(line)

    Hook method executed just before the command line line is interpreted, but
after the input prompt is generated and issued. This method is a stub in Cmd;
it exists to be overridden by subclasses. The return value is used as
the command which will be executed by the onecmd() method; the precmd()
implementation may re-write the command or simply return line unchanged.

Cmd.postcmd(stop, line)

    Hook method executed just after a command dispatch is finished.
This method is a stub in Cmd; it exists to be overridden by subclasses.
line is the command line which was executed, and stop is a flag which
indicates whether execution will be terminated after the call to postcmd();
this will be the return value of the onecmd() method. The return value of
this method will be used as the new value for the internal flag which
corresponds to stop; returning false will cause interpretation to continue.
Enter fullscreen mode Exit fullscreen mode

Ok, so enough talking

Since I prefer using Python over other languages I'm going to change the name of my cmd class to MazeCrawler because snakes does not run, yeah? But still, it's little bit boring. I'm gonna go little bit further and rename it to (with regards to It's always sunny in Philadelphia) : MazeKroller

import cmd                                                                                                                                                                                                   

class MazeKroller(cmd.Cmd):
    """Interface for walking through the maze

    https://coding-challanges.herokuapp.com/challanges/maze
    """                                                                                                                                                  

    intro = "Welcome to Maze shell. Type `?' or `help' to list commands.\n"
    prompt = '~ '  # Because it's a snake!
Enter fullscreen mode Exit fullscreen mode

For now we have only one method -- it is init. And we have to implement rest of the REST.
We need to be able to:

  • create the maze,
  • navigate through it
  • and pick up the coins to win (actually to get out from the maze). Also a couple of methods should be implemented for usability: get info and give up. Here is the stubs for it:
class MazeKroller(cmd.Cmd):
    """Interface for crawling through the maze

    https://coding-challanges.herokuapp.com/challanges/maze
    """
    intro = "Welcome to Maze shell. Type `?' or `help' to list commands.\n"
    prompt = "~ "

    def do_init(self, user, password):
        """Credentials for BasicHTTPAuth."""
        self.user = user
        self.password = password

    def do_create_maze(self, size):
        """Create a new maze.
        Now you have 3600 seconds to escape.

        POST /api/mazes\n"""

    def do_info(self, maze_id):
        """Get some general data about your maze

        GET - /api/mazes/{id}\n""" 

    def do_get_cells(self, maze_id):
        """See the cells around you.

        GET - /api/mazes/{id}/steps\n"""

    def do_get_coin(self):
        """Pick up a coin.

        POST - /api/mazes/{id}/coins\n"""

    def do_giveup(self):
        """Gives up the game.

        DELETE /api/mazes/{id}\n"""

Enter fullscreen mode Exit fullscreen mode

So here we have a basic class for our MazeKroller.
This class does nothing right now, but could be instantiated with

MazeKroller().cmdloop()
Enter fullscreen mode Exit fullscreen mode

Play around little bit with it and in the next post I will provide you with implementation of that methods.

Stay tuned!

Discussion (2)

Collapse
kip13 profile image
kip

Python always surprise me with their modules! Thanks for sharing this one.

Collapse
alexbender profile image
Alex Bender Author

Exactly! You're welcome!