drwhut / tabletop-club

An open-source platform for playing tabletop games in a physics-based 3D environment for Windows, macOS, and Linux! Made with the Godot Engine.
https://tabletopclub.net
MIT License
1.3k stars 57 forks source link

Spawn pre-defined pieces on boards via the context menu. #229

Open GrimPixel opened 1 year ago

GrimPixel commented 1 year ago

Is your feature request related to a problem? Please describe. Currently, a game requires a tc file, which is not easy to set up.

Describe the solution you'd like Pieces can be spawned on boards at predefined positions through the right-click menu of a board. Positions can be defined like the snaps mentioned in https://github.com/drwhut/tabletop-club/issues/180. A board may have different predefined setups, e.g. different draughts, chess, reversi on the same 8x8 board. It could also be defined vertically, so games like Mahjong Solitaire can be played as well.

In addition, there could be an option of randomness, for the board to spawn games like Klondike, FreeCell, different Mahjong, etc.

drwhut commented 1 year ago

I could see this being done as an extension of the snap system.

GrimPixel commented 1 year ago

It is also noticeable that after playing a game, pieces or tiles may be everywhere and it requires some mouse clicks to clean up the table. So it would be better to also have the ability to detect and use existing objects on the table, instead of spawning a new set.

drwhut commented 1 year ago

That's a good idea - it would provide a good way to reset a game that doesn't leave any clutter.

GrimPixel commented 1 year ago

I'd like to add that there could be vertical positions, so mahjong tiles can be arranged the same as in real life and Mahjong Solitaire can be played.

About the question of how to define positions of objects, there is one thing to be noticed: the sizes of an object differ from one asset set to another (e.g. mahjong tiles in related games by GNOME and KDE). If positions of objects are defined by coordinates, then after a change of asset set, the “Vector3” values are changed, and the objects may collide with each other and fly out of their original positions like an explosion. As a result, relative positions may be considered: define the x, y, z distances of an object to the centre of the table, with the ability of reading “Vector3” values of an object and setting the distance correspondingly. For example, a 4x3 Shisen-Shō game can be defined like

(-3x/2, y/2, z), (-x/2, y/2, z), (x/2, y/2, z), (3x/2, y/2, z),
(-3x/2, y/2, 0), (-x/2, y/2, 0), (x/2, y/2, 0), (3x/2, y/2, 0), 
(-3x/2, y/2, -z), (-x/2, y/2, -z), (x/2, y/2, -z), (3x/2, y/2, -z),
drwhut commented 1 year ago

I'd like to add that there could be vertical positions, so mahjong tiles can be arranged the same as in real life and Mahjong Solitaire can be played.

I was thinking about this when it comes to, for example, Knights in a Chess game. The knights need to be able to be facing either forwards or backwards. So it stands to reason that they could also be pointing "up" or "down" so that Mahjong pieces can be oriented in the intended way. Maybe this can be done in-game by having the piece snap to the nearest axis when it is put on the board? But this would not account for the "setup" that this issue describes, the starting orientation would have to be in the config.cfg file somewhere.

On your point about absolute vs relative positions for the snap points, I can see where you are coming from, but I feel like it might introduce a lot more room for error when it comes to the implementation in order to avoid an issue that I think will very rarely be come across. I imagine that in the vast majority of cases, the pieces that are intended to be put on a specific board will come in the same asset pack as the board itself - and if they are indeed in differing asset packs, then one pack is then in a way "dependent" on the other, and thus it falls to the owner of the "parent" pack to let the other creator know that there has been a change that may be relevant to them.

I guess my main concern is that there may be a number of edge-cases that arise from having a system relative to the sizes of the pieces, given that each of those pieces can potentially be various sizes. For me, it also makes more sense to have co-ordinates relative to the center of the board itself, since most boards are tile-based anyway, a lot of the time the pieces will want to end up in the center of each tile (which is where I imagine the snap point would be defined in the config.cfg file). Then, the size of the piece that is being snapped to that position can have it's bounding box analyzed to make sure it is centered on that tile, regardless of the size of the piece itself.

Would like to hear your thoughts on this - I may very well be tunnel-visioning on what I imagined the implementation would look like.

GrimPixel commented 1 year ago

That orientation problem was what I neglected. Yes, the orientations of pieces and other objects need to be defined in the board's config.cfg file, or a new cfg file dedicated to that. In the game of “Quilt”, cards are arranged in varying orientations. So I guess the simplest solution is a Vector6 for an object. https://commons.wikimedia.org/wiki/File:Carpet_patience_1.jpg

About that relative-position problem, here is the background: there are different sets of images, for example, 43 sets of French-suited playing card images. If they all appear on the menu dropdown list, they will occupy too much room. So I created subdirectories in /cards, picked one of them as the default one, moved it and itsconfig.cfg, stack.cfg files into /cards, and placed an empty file default in that subdirectory. If the player wants to use another set of images, he/she can move the default images and the config.cfg, stack.cfg files into the subdirectory of the default asset set, and then move another image set and its config.cfg, stack.cfg files out. That means that after such a change, Vector2 values in cards/config.cfg may be changed as well. This means that cards spawned on the board will have different distances to each other. For mahjong, where tiles have even more significant size differences, as the Chinese tiles are 1.5 times in length of the Japanese tiles, relative distances to the board centre and that bounding box solution are not enough, as tiles need to be densely packed. If each set of images of cards or tiles needs to have a corresponding board/config.cfg, that could mean a lot of work when there are many sets of card or tile images of different sizes.

drwhut commented 1 year ago

So I guess the simplest solution is a Vector6 for an object.

It would have to be one Vector3 for the position, and one Vector3 for the rotation in degrees, but yeah.

About that relative-position problem, here is the background: there are different sets of images, for example, 43 sets of French-suited playing card images. If they all appear on the menu dropdown list, they will occupy too much room.

Which menu drop-down are you referring to here? What I was imagining for this issue is that there's just one sub-menu added to the context menu for boards, which lists a set of pre-defined starting states for the board as defined in the config.cfg file (not in separate folders). For example, the Chess board could have starting states for both "Chess" and "Draughts", among other games that use an 8x8 board. The drop-down menu should not be that big unless the creator makes a ton of starting states for the board.

GrimPixel commented 1 year ago

Oh, that dropdown menu was the asset selection menu, where there is only a “TabletopClub” by default. In PySolFC, such an image set is called a “cardset” and can be selected in “Options → Cardset”. That solution doesn't fully fit this situation, where assets are loaded at the initialisation and the restart take some time. Or maybe new image sets can be loaded ingame. I also neglected that considering the case of spawning chess and draughts on the same board. So there are two situations: objects are placed at specific locations on the board; objects are densly packed on the board. The first requires absolute positions on the board; the second requires relative positions to the centre of the board with consideration on object sizes. To play games with gravity, the finaisation like rotating the objects and then locking the board is required. The board in this case is not a mere cuboid, but with a guard at its bottom, and the tiles need to be aligned with the guard. https://www.youtube.com/watch?v=RRwuswkk9GI

drwhut commented 1 year ago

Oh, that dropdown menu was the asset selection menu, where there is only a “TabletopClub” by default.

Ah right, I think I understand your query now. However, I think it strays quite a bit from this issue specifically, hence the confusion I was having. If you want to discuss having multiple "card sets" in an asset pack, I would happily do so in another issue.

I also neglected that considering the case of spawning chess and draughts on the same board. So there are two situations: objects are placed at specific locations on the board; objects are densly packed on the board. The first requires absolute positions on the board; the second requires relative positions to the centre of the board with consideration on object sizes.

Considering the fact that in the vast majority of cases, we want the pieces to be centered in the middle of each of the tiles of the board, I think it makes sense to use absolute positions. I'm not quite sure how the pieces would be centered if relative positions were used, and they would potentially change depending on the size of the pieces themselves.

Maybe both could be implemented, but that would be very confusing from a usability standpoint I think.

GrimPixel commented 1 year ago

Another way: use absolute positions only, with collision avoidance that places the tile to the nearest possible position without collision. So it can be defined like (0, 0, 0), (-0.1, 0, 0), (0.1, 0, 0), (-0.2, 0, 0), (0.2, 0, 0), which means tiles are bound to be colliding originally and with the collision avoidance, they are aligned next to each other without problem.

drwhut commented 1 year ago

I'm confused... I think we've got two totally different ideas in our minds as to how this would work. I'm not sure why the system would need to take the piece's bounding boxes into account when placing them on the board.

Would it be possible for you to describe in detail what you imagine this system would look like, both from the asset creator's perspective (i.e. what data is in the config file), and from the player's perspective in how they interact with the system?

GrimPixel commented 1 year ago

The “collision avoidance” is something imagined: it prevents collisions of objects on defined spawning positions, by means of detecting the nearest possible position that doesn't collide with existing ones for the new object. To define a compact layout, the creator can simply write small distances without worrying about collisions.

GrimPixel commented 1 year ago

I found that collision avoidance solution not ideal: it is impossible to create layouts where gaps exist or half a tile shifted, when the size of tiles undetermined is: https://upload.wikimedia.org/wikipedia/commons/thumb/d/d6/Online_Solitaire_Mahjong_Spiel_von_ratehase.de.png/620px-Online_Solitaire_Mahjong_Spiel_von_ratehase.de.png

GrimPixel commented 8 months ago

The solution for the corresponding positions.cfg file may be like

[Chess]
positions = {
  {black rook, black knight, black bishop, black queen, black king, black bishop, black knight, black rook}, 
  {black pawn, black pawn, black pawn, black pawn, black pawn, black pawn, black pawn},
  {, , , , , , }, 
  {, , , , , , }, 
  {, , , , , , }, 
  {, , , , , , }, 
  {white pawn, white pawn, white pawn, white pawn, white pawn, white pawn, white pawn},
  {white rook, white knight, white bishop, white queen, white king, white bishop, white knight, white rook}, 
}

In the corresponding config.cfg file for the board without any border like https://upload.wikimedia.org/wikipedia/commons/5/5b/Chessboard480.png:

[Board.png]
yaw_angle = 0
grid_shape = square
grid_endpoints = {
  (1/16, 1/16), (15/16, 15/16)
}

Defining endpoints is like drawing a rectangle with mouse. Grids are generated by dividing the space evenly for “square” ones. For a different board like https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Brett_Schach_Schwarz-Wei%C3%9F.png/477px-Brett_Schach_Schwarz-Wei%C3%9F.png, where there are borders, the grid_endpoints values will be different.

Other grid shapes like that for “Chinese chequers” https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Chinese_checkers_start.svg/431px-Chinese_checkers_start.svg.png can be defined in grid_shapes.cfg like

[Standard]
points = {
  (12/24, 0/16), 
  (11/24, 1/16), (13/24, 1/16), 
  (10/24, 2/16), (12/24, 2/16), (14/24, 2/16), 
  ...
}
endpoints = {
  (0/24, 4/16), (24/24, 12/16)
}
drwhut commented 8 months ago

Hmm... I'm not sure about having all of the data in separate files, but having a "shortcut" for defining grids instead of listing each co-ordinate would be very helpful for a lot of boards. And there would still be a way to define points for non-grid boards as well like the one you mentioned.

GrimPixel commented 8 months ago

If they are integrated into fewer files, I think each cfg file needs to be brief.

To make it clear, I prefer to use the word “mesh” instead of “grid” now, to describe the shape of the collection of all available positions on a board.

There is no stacks.cfg for a board game, but could be a layouts.cfg, where two things are defined:

  1. all available positions on every board,
  2. all spawning positions of every object on every board,
  3. two endpoints of every mesh.

In layout.cfg

[Chess]
endpoints = {(0, 0), (1, 1)}
positions = {
  (0/7, 0/7), (1/7, 0/7), (2/7, 0/7), ...
}
spawn_points = {
  'standard chess' = {
    'black rook' = {(0/7, 0/7), (7/7, 0/7)},
    ...
  }
}

There could also be connections to connect positions with connection_type, making the “mesh” real meshes. This will be useful for AI models, I think. The mesh can also be visualised on the board.

In config.cfg

[chess board.webp]
mesh = Chess
mesh_endpoints = {(1/16, 1/16), (15/16, 15/16)}