py3minepi / py3minepi-legacy

Original attempt to port Minecraft Pi to Python 3
https://github.com/py3minepi/py3minepi
Other
25 stars 7 forks source link

Top down development of high-level API #30

Open jonathanfine opened 9 years ago

jonathanfine commented 9 years ago

I've started exploring the design of the high-level API. My basic idea it to write user code that feels right, and then to provide a stub implementation.

For example, here's how to get going


import minecraft
import minecraft.block as block

world = minecraft.connect()
# Set the type of a block.
world[3, 4, 5] = block.DIRT

You can see the whole user sample user file at smoke.py

To reduce distractions while I work I'm doing this in a fork of the main repository, but in my own github account.

I welcome comments, and if it helps to do this you can create a pull request.

dbrgn commented 9 years ago

I'm not convinced about the indexing syntax yet. This just covers a small part of the API.

What happens if you call world[3, 4, 5] without an assignment? Does it return the block type?

How do you get the height of a specific place? world[3, 4].get_height()? That won't be possible if the indexing syntax returns a block type (currently an int or enum).

Furthermore, world[3, 4] is probably wrong, correct would be world[3, _, 4] because the coordinates are xyz and z is "up" while y is "forward/back".

Last but not least, this will lead to a lot of hacks in the codebase. I'm more for a function or method based approach.

ghickman commented 9 years ago

I'd really like to avoid an explicit connection to the world. I believe this should be handled by the instantiation of a Minecraft class.

dbrgn commented 9 years ago

I'd really like to avoid an explicit connection to the world. I believe this should be handled by the instantiation of a Minecraft class.

I disagree about that. That's global connection state. It makes testing harder, creates side effects and makes it hard or impossible to control multiple minecraft instances at once.

A lot of projects (e.g. Django or Celery) moved from implicit initialization to explicit initialization (with a very simple default) in the last 1-2 years.

doismellburning commented 9 years ago

I'd really like to avoid an explicit connection to the world. I believe this should be handled by the instantiation of a Minecraft class.

I disagree about that. That's global connection state. It makes testing harder, creates side effects and makes it hard or impossible to control multiple minecraft instances at once.

Surely that's nothing global, that's a connection per Minecraft instance, no?

dbrgn commented 9 years ago

Aaah, sorry, I didn't get that :)

In that case it would be:

from minecraft import Minecraft

world = Minecraft()
# you're connected!

Right?

ghickman commented 9 years ago

@dbrgn – Yes that's the API I was going for, sorry I should have added an example to be specific.

jonathanfine commented 9 years ago

@dbrgn

You wrote

What happens if you call world[3, 4, 5] without an assignment? Does it return the block type?

Did you follow the link I gave to

whole user sample user file at smoke.py

The answer is YES.

dbrgn commented 9 years ago

whole user sample user file at smoke.py

Sorry, I overlooked that :)

In that case: How would you get the height of a specific x-z coordinate in the world? You can't attach any methods to the indexed world call, as it returns an enum type (which is an integer).

bennuttall commented 9 years ago
from minecraft import Minecraft

world = Minecraft()
# you're connected!

:+1:

jonathanfine commented 9 years ago

@dbrgn asked

How would you get the height of a specific x-z coordinate in the world?

A really good API is one where the answer to this question is obvious, from the basic examples given in my sample file smoke.py.

A good API is one where the answer is obviously the only correct one, once you have seen it. I'm leaving your question as an exercise. I'll tell you what my answer is this evening.

You'll appreciate my answer better if you try to answer the question yourself. (There, I sound like a teacher.)

dbrgn commented 9 years ago

It is definitely not obvious.

You could go with world[1, 4]. But then it's not necessarily obvious whether these are xy or xz coordinates.

You could go with something like world[1, ..., 4], similar to the way numpy does it.

The return value is also non-obvious. It could return either the height as an integer, or - if you consider the syntax a slice - a sequence of blocks that matches the coordinates (in the case of world[1, ..., 4] all blocks that are in the vertical column from the lowest to the highest block, at the xz coordinates (x=1, z=4).

jonathanfine commented 9 years ago

@dbrgn Hint. An object can have attributes (besides __getitem__ and __setitem__).

dbrgn commented 9 years ago

You do sound like a teacher. And it makes it hard to provide good feedback.

jonathanfine commented 9 years ago

@dbrgn

We have world[1, 2, 3]. So how about height[1, 2]. And to make it easier to use a position in space rather than the horizontal plane, also allow height[1, 2, 3].

You were trying to use the [...] interface directly on world, which made the problem impossible. But introduce height and it's easy.

One last thing - height is an attribute of world.

h = world.height[pos]
dbrgn commented 9 years ago

While I like the concept that the world is indexable (treating it as a 3D array), i'm not too fond of having height as an attribute of world. There is no hierarchical relation between world and its height. A height is also nothing with a spacial location, it's rather something like a function call. And function calls traditionally use parentheses, not square brackets.

A few thoughts (not all of them well thought-out):

from minecraft import Minecraft

mc = Minecraft()
some_position = mc.world[1, 2, 3]
print(some_position.height)
print(some_position.type)
print(some_position.hits)

That would be the OOP style approach. I'm not completely sure yet whether it's better than a functional approach though.

from minecraft import BlockGroup, BlockType

# Positions could be indicated by tuples
group = BlockGroup((1, 2, 3), (4, 5, 6), (7, 8, 9))
group.type = BlockType.WOOD

# Or maybe by a separate type? We had a discussion about this in a separate issue.
group = BlockGroup(Position(1, 2, 3), Position(4, 5, 6), Position(7, 8, 9))

# A group could act like a set. That would allow things like intersections or differences.
group.add((1, 3, 5))

# We could define different operations
group.destroy()  # Remove blocks (replace with air)
group.translate(0, 3, 0)  # Move up by three blocks
pozorvlak commented 9 years ago

We could treat BlockGroups as iterators:

group = BlockGroup((1, 2, 3), (4, 5, 6), (7, 8, 9))
for block in group:
    block.type = BlockType.WOOD

Does the low-level API allow us to issue commands affecting more than one block, or would we have to implement eg group.type = BlockType.WOOD as a for-loop internally?

dbrgn commented 9 years ago

Does the low-level API allow us to issue commands affecting more than one block, or would we have to implement eg group.type = BlockType.WOOD as a for-loop internally?

There is a setBlocks command: https://github.com/py3minepi/py3minepi/wiki/Minecraft-Pi-Protocol-Specification#commands

pozorvlak commented 9 years ago

OK, then it definitely makes sense to allow some whole-group operations in the high-level API (though I think we could usefully make them iterable, too).

dbrgn commented 9 years ago

That would be trivial by implementing __iter__.

pozorvlak commented 9 years ago

Absolutely! I'm just saying that we should do so :-)

On Thu, Oct 2, 2014 at 9:25 PM, Danilo Bargen notifications@github.com wrote:

That would be trivial by implementing iter.

— Reply to this email directly or view it on GitHub https://github.com/py3minepi/py3minepi/issues/30#issuecomment-57699447.