Actions

Difference between revisions of "Gameboard"

From Future Skill

 
(11 intermediate revisions by the same user not shown)
Line 18: Line 18:
 
from gameboard_helpers import CommonSetups, Style
 
from gameboard_helpers import CommonSetups, Style
  
tiles = {
+
class Cell:
   'grass': {'color': 'green', 'line_color': 'darkgreen'},
+
   def __init__(self, color, line_color):
  'mud': {'color': 'brown','line_color': 'darkgrey'}
+
    self.color = color
}
+
    self.line_color = line_color
 +
 
 +
grass = Cell('green', 'darkgreen')
 +
mud = Cell('brown', 'darkgrey')
  
 
class Challenge:
 
class Challenge:
Line 29: Line 32:
 
     CommonSetups.square(board)
 
     CommonSetups.square(board)
 
     Style.topdown(board, context.canvas, tiles)
 
     Style.topdown(board, context.canvas, tiles)
     board.fill(10, 10, {'tile_id'='grass'})
+
     board.fill(10, 10, grass)
     board.add_cell(2, 3, {'tile_id'='mud'})
+
     board.add_cell(2, 3, mud)
 
     fit_board_into(board, 5, 5, 10, 10)
 
     fit_board_into(board, 5, 5, 10, 10)
 
</nowiki>
 
</nowiki>
Line 46: Line 49:
  
 
===Cells===
 
===Cells===
A location on the board is called a ''cell'', consisting of a coordinate and some properties associated with it like a ''tile_id''. You may add cells in any configuration, so non-square and irregular boards are allowed.
+
A location on the board is called a ''cell'', consisting of a coordinate and an object describing the attributes. You may add cells in any configuration, so non-square and irregular boards are allowed.
Use ''add_cell(x, y, tile_info)'' to add cells. ''tile_info'' is a dictionary that is required to contain a ''tile_id''. The tile ID is used to construct the visuals for the cell, explained in the [[Gameboard#Visuals|Visuals]] section.
+
Use ''add_cell(x, y, cell)'' to add cells. The ''cell'' object may hold any set of attributes you may need. Some specific attributes may be required by the predefined tile factories, described [[Gameboard#Visuals|Visuals]] section. The ''cell'' is examined by duck typing and is not expected to be derived of a certain class.
 +
 
 +
The method ''fill(width, height, cell, x=0, y=0)'' fills a square area; useful for quickly creating a square gameboard or rooms within one.
 +
 
 +
===Layout===
 +
There are two important concepts for the layout of the board: the spatial transformation of coordinates and the neighbor deltas. This section isn't strictly necessary to understand in depth, but it's useful if you're trying to customize a board beyond the ''CommonSetups'' and ''Style'' helpers.
 +
 
 +
====Matrices====
 +
Some linear algebra is used to transform a cell coordinate into canvas coordinates, but no deeper understanding of it is required to configure the game board. There are three ''coordinate spaces'': cell, board and local canvas. The ''GameBoard'' has two matrices called ''basis'' and ''projection'' which are used to transform points between the coordinate spaces. [TODO: Link to and explain math2d lib.]
 +
 
 +
The cell coordinate space is what the game logic refers to as you add cells and place game pieces on the board. This is most often an integer pair.
 +
 
 +
Multiplying the cell coordinate with the ''basis'' matrix gives you a point in ''board space''. Its primary use is to give a 'flatter' space that is useful when dealing with hexagonal cells. The cell and board spaces will likely look exactly the same for square grids, using an identity matrix.
 +
 
 +
The ''projection'' matrix makes things fancy. It can take the flat board space and rotate and skew it into any ''orthographic'' view. It only applies to the position of game pieces and will not skew the game piece graphics.
  
 
===Visuals===
 
===Visuals===
 
The GameBoard constructs the tile visuals when adding a cell using the ''tile_factory'' function. The ''Style'' class assigns this for you, but if the prebuild styles doesn't cover your needs you may wish to make your own ''tile_factory'' function and assign it to the GameBoard.
 
The GameBoard constructs the tile visuals when adding a cell using the ''tile_factory'' function. The ''Style'' class assigns this for you, but if the prebuild styles doesn't cover your needs you may wish to make your own ''tile_factory'' function and assign it to the GameBoard.
  
''tile_factory'' should simply take a ''tile_id'' as an argument and return a new GraphicalObject that is the visual representation of this tile.
+
The ''tile_factory'' function should have the arguments 'x', 'y' and 'cell', and return a new GraphicalObject that is the visual representation of this tile.
You'll likely need access to the ''canvas'' and other information about the board when creating a tile; To achieve this you may either wrap the configuration of the ''tile_factory'' in a closure or create a callable class using a ''__call__(self, tile_id)'' method.
+
You'll likely need access to the ''canvas'' and other information about the board when creating a tile; To achieve this you may either wrap the configuration of the ''tile_factory'' in a closure or create a callable class using a ''__call__(self, x, y, cell)'' method.
 
<div style="background: #F0F0F0">
 
<div style="background: #F0F0F0">
 
  <nowiki>
 
  <nowiki>
 +
class Cell:
 +
  def __init__(self, color):
 +
    self.color = color
 +
 +
grass = Cell('green')
 +
 
# The outer closure takes the additional parameters and returns the actual tile_factory function that has access to the closures arguments.
 
# The outer closure takes the additional parameters and returns the actual tile_factory function that has access to the closures arguments.
 
def configure_tile_factory(canvas):
 
def configure_tile_factory(canvas):
   def tile_factory(tile_id):
+
   def tile_factory(x, y, cell):
     color = "#40FF40" if tile_id == "grass" else "#505050"
+
     color = getattr(cell, 'color', "#505050")
 
     return canvas.new_circle(1.0, color)
 
     return canvas.new_circle(1.0, color)
 
   return tile_factory
 
   return tile_factory
Line 67: Line 90:
 
     board = GameBoard()
 
     board = GameBoard()
 
     board.tile_factory = configure_tile_factory(context.canvas)
 
     board.tile_factory = configure_tile_factory(context.canvas)
 +
    board.add_cell(0, 0, grass)
 
</nowiki>
 
</nowiki>
 
</div>
 
</div>
 +
 +
The returned GraphicalObject will be managed by the GameBoard and placed automatically.
 +
 +
===Helper tile_factory functions===
 +
The ''gameboard_helper'' contains a set of predefined tile_factory functions, used by the ''Style'' class but available on their own if needed.
 +
 +
'''configure_square_tile_factory''' creates square tiles that can be used with any projection, like topdown, isometric or any other orthographic layout. The graphics are simple flat polygons, useful for quick prototyping without the need for more detailed graphics. The cell object is expected to contain 'color' and 'line_color'.
 +
 +
'''configure_hex_tile_factory''' works like the square counterpart, except with hexagonal tiles.
  
 
== GamePiece ==
 
== GamePiece ==
Line 132: Line 165:
 
The generator will consider walls as blocking if using a ''WalledGameBoard'' and setting the third argument ''wall_check'' to true (default).
 
The generator will consider walls as blocking if using a ''WalledGameBoard'' and setting the third argument ''wall_check'' to true (default).
  
It will also consider node weights if added to a cell, resulting in paths avoiding higher weight tiles without blocking them completely.
+
It will also set node weights if the cell has a ''weight'' attribute (defaults to 1.0 otherwise), resulting in paths avoiding higher weight tiles without blocking them completely.
<nowiki>board.add_cell(x, y, {'tile_id':'grass', 'weight': 2.0})</nowiki>
+
 
 +
[TODO] Explain collision groups.
  
 
===Building a graph===
 
===Building a graph===

Latest revision as of 09:56, 12 April 2021

[DRAFT]

The GameBoard library

A reusable and configurable system for game-like challenges.

[TODO]

  • Board quick setup
  • Creating and placing a gamepiece
  • Pathfinding
  • Styling concepts
  • Custom board setup

GameBoard quick setup

Here's an example to quickly get up and running:

from gameboard import GameBoard
from gameboard_helpers import CommonSetups, Style

class Cell:
  def __init__(self, color, line_color):
    self.color = color
    self.line_color = line_color

grass = Cell('green', 'darkgreen')
mud = Cell('brown', 'darkgrey')

class Challenge:
  fn __init__(self, context):
    ...
    board = GameBoard()
    CommonSetups.square(board)
    Style.topdown(board, context.canvas, tiles)
    board.fill(10, 10, grass)
    board.add_cell(2, 3, mud)
    fit_board_into(board, 5, 5, 10, 10)

The CommonSetups class configures the board to use a square tiled grid with four directions. The Styles class then configures how the board is displayed: the look of the tiles and how they are laid out. [TODO] add link to documentation for CommonSetups and Styles.

You can fit the board into view with the function fit_board_into(board, x, y, w, h), provided in the gameboard_helpers module.

The GameBoard library

GameBoard

A GameBoard can be used to represent a playfield with evenly spaced cells (like square or hexagonal). Using the two matrices base and projection you're able to configure a multitude of orthographic styles. The property neighbor_deltas gives a list of how cells are connected and is mainly used for pathfinding.

Cells

A location on the board is called a cell, consisting of a coordinate and an object describing the attributes. You may add cells in any configuration, so non-square and irregular boards are allowed. Use add_cell(x, y, cell) to add cells. The cell object may hold any set of attributes you may need. Some specific attributes may be required by the predefined tile factories, described Visuals section. The cell is examined by duck typing and is not expected to be derived of a certain class.

The method fill(width, height, cell, x=0, y=0) fills a square area; useful for quickly creating a square gameboard or rooms within one.

Layout

There are two important concepts for the layout of the board: the spatial transformation of coordinates and the neighbor deltas. This section isn't strictly necessary to understand in depth, but it's useful if you're trying to customize a board beyond the CommonSetups and Style helpers.

Matrices

Some linear algebra is used to transform a cell coordinate into canvas coordinates, but no deeper understanding of it is required to configure the game board. There are three coordinate spaces: cell, board and local canvas. The GameBoard has two matrices called basis and projection which are used to transform points between the coordinate spaces. [TODO: Link to and explain math2d lib.]

The cell coordinate space is what the game logic refers to as you add cells and place game pieces on the board. This is most often an integer pair.

Multiplying the cell coordinate with the basis matrix gives you a point in board space. Its primary use is to give a 'flatter' space that is useful when dealing with hexagonal cells. The cell and board spaces will likely look exactly the same for square grids, using an identity matrix.

The projection matrix makes things fancy. It can take the flat board space and rotate and skew it into any orthographic view. It only applies to the position of game pieces and will not skew the game piece graphics.

Visuals

The GameBoard constructs the tile visuals when adding a cell using the tile_factory function. The Style class assigns this for you, but if the prebuild styles doesn't cover your needs you may wish to make your own tile_factory function and assign it to the GameBoard.

The tile_factory function should have the arguments 'x', 'y' and 'cell', and return a new GraphicalObject that is the visual representation of this tile. You'll likely need access to the canvas and other information about the board when creating a tile; To achieve this you may either wrap the configuration of the tile_factory in a closure or create a callable class using a __call__(self, x, y, cell) method.

class Cell:
  def __init__(self, color):
     self.color = color

grass = Cell('green')

# The outer closure takes the additional parameters and returns the actual tile_factory function that has access to the closures arguments.
def configure_tile_factory(canvas):
  def tile_factory(x, y, cell):
    color = getattr(cell, 'color', "#505050")
    return canvas.new_circle(1.0, color)
  return tile_factory

class Challenge:
  def __init__(self, context):
    board = GameBoard()
    board.tile_factory = configure_tile_factory(context.canvas)
    board.add_cell(0, 0, grass)

The returned GraphicalObject will be managed by the GameBoard and placed automatically.

Helper tile_factory functions

The gameboard_helper contains a set of predefined tile_factory functions, used by the Style class but available on their own if needed.

configure_square_tile_factory creates square tiles that can be used with any projection, like topdown, isometric or any other orthographic layout. The graphics are simple flat polygons, useful for quick prototyping without the need for more detailed graphics. The cell object is expected to contain 'color' and 'line_color'.

configure_hex_tile_factory works like the square counterpart, except with hexagonal tiles.

GamePiece

The GamePiece class should be used for anything that can be placed on the GameBoard.

from gameboard import GameBoard, GamePiece

class Challenge:
  def __init__(self, context):
    board = GameBoard()
    gfx = context.canvas.new_circle(1.0, '#FF00FF')
    actor = GamePiece(gfx)
    board.place_game_piece(actor, 1, 1)

You can use it as-is for many cases or create your own derived class for more flexibility.

Pathfinding

The library comes with some general pathfinding tools that are easily configured with functions in the gameboard_helper module. The Pathfinding class, located in the gameboard module lets you construct a graph and traverse it with an implementation of the AStar algorithm. It's a very generalized implementation, though nodes are denoted with integer coordinates (x,y).

Quick setup

from gameboard import GameBoard
from gameboard_helpers import generate_board_pathfinding

class Challenge:
  def __init__(self, context):
    board = GameBoard()
    ...
    pathfinding = generate_board_pathfinding(board)

GamePiece pathfinding

You may pass the resulting pathfinding instance to one or more GamePieces. Doing this enables the step_towards method that makes it very simple to traverse the board.

from gameboard import GameBoard, GamePiece
from gameboard_helpers import generate_board_pathfinding

class Challenge:
  def __init__(self, context):
    board = GameBoard()
    ...
    pathfinding = generate_board_pathfinding(board)
    self.piece = GamePiece(gfx, pathfinding)
    board.place_game_piece(self.piece, 0, 0)

  def step(self):
    self.piece.step_towards(4, 5)

Each time step_towards is called the game piece is moved one step towards the target by referencing the pathfinding graph provided to the game piece's constructor. The method returns true if a step was taken, false otherwise. You may have to refer to the game piece's coordinate to see if it arrived at the goal or if it was unable to move.

get_possible_moves returns a list of coordinates a game piece may move to, based on the pathfinding graph.

Helper for generation

The generate_board_pathfinding will likely cover most uses. By default it takes the neighbor_delta from the board itself to generate connections between cells. If movement differs between characters you may provide a move set as the second argument. The generator will consider walls as blocking if using a WalledGameBoard and setting the third argument wall_check to true (default).

It will also set node weights if the cell has a weight attribute (defaults to 1.0 otherwise), resulting in paths avoiding higher weight tiles without blocking them completely.

[TODO] Explain collision groups.

Building a graph

In case the provided generate_board_pathfinding isn't sufficient you may construct your own Pathfinding graph. The Pathfinding class provides methods for adding nodes and connecting them. It's not coupled with the gameboard in anyway and keeps track of its own graph of nodes. [TODO] Explain the dynamic check function.