-
Notifications
You must be signed in to change notification settings - Fork 1
How the NEW Chessboard works
The old chessboard had a lot of parts coming together to make it work, and with a lot of strange logic choices. For example, a BoardPiece object could contain an aPiece object, which could then contain the same BoardPiece object, which would contain the same aPiece object, ad infinitum.
That's just one flaw with the old system, but really the biggest flaw was readability. It was very difficult to actually find what you were looking for when trying to add something to the game because everything was nested in a bunch of different function calls. This new chessboard logic is designed to completely simplify that.
These are the objects used for the new chessboard logic:
- NewBoardSpawner
- obj_piece
- obj_tile
The first step is to actually create the board, which means generating the tiles and the pieces.
It uses two for-loops to create the tiles on each column, and then move to the next row. The "chr" function and "ord" function are used in tandem to increase the tile letter based on the character's ASCII value. As it's looping, if a tile is in a position that has been designated to have a piece on it by default, it will run the spawn_piece function.
First, the piece is created on the same x and y position as the tile it's on. Second, it's set to be either the opponent's piece or the player's piece. Third, it's decided what piece it actually is. Fourth, its positional values are updated. Lastly, the tile is updated to actually contain the piece.
This is the object the new board uses for the chess pieces used during gameplay. Each variable is pretty self-explanatory:
- belongs_to: Does the piece belong to the player or the opponent
- piece_name: What piece is it
- row_number: The row number the piece is on
- col_letter: The column letter the piece is on
- image_speed: Built in GameMaker variable, used to set animation speed, with 0 meaning no movement
- image_index: Built in GameMaker variable, used to set a specific frame of an animation
The Draw event of the object updates the sprite depending on which piece it is, and then also increases the image_index value if the piece should be white instead of black.
This is where the bulk of the code for this update is contained. It has a VERY long function, called "mark_valid_tiles," which is what's used for movement logic, but what it lacks in brevity it makes up for in legibility.
Similar to obj_piece, the variables for this object are fairly obvious:
- row_number: The row number the piece is on
- col_letter: The column letter the piece is on
- piece_contained: The piece that is currently on the tile instance, set to noone if there is no piece
- valid_tile: Used to determine if the game should draw the sprite for a valid movement on a tile
- global.selected_piece: Tracks whatever piece the player selected
The Draw event for the tile is really simple. It draws the tile itself first, then it draws the tile's position label, and then if the tile is marked as valid it draws the sprite for a valid tile on top of itself.
The Mouse Left Pressed event already has comments in the code, so I won't add much detail. It first checks if the tile that was clicked was marked as a valid tile, and then if it wasn't it checks if it instead was a tile with a piece on it.
Now I'll explain the main functions that the object has currently.
The clear_valid_tiles function simply loops through all instances of obj_tile that exists in the room, and sets their valid_tile variable to false.
The mark_valid_tiles function contains the movement calls for all of the different piece types. A piece can then call one of two different functions depending on how it moves, those being mark_tiles and mark_path.
The comments explain what the function does at the beginning. Afterwards, a switch-statement is used to determine what type of piece is being viewed.
Here's an example of how the Knight piece works with the mark_tiles function. The knight_tiles variable is an array that holds the struct for movement. Think of it as an array of all of the coordinates the piece can move to when selected. The struct format is used for parity between the row and col variables. After all the coordinates are set in the array, the mark_tiles function is called and is passed the positions and who the piece belongs to.
The mark_tiles function is set to loop through the length of the coordinate array passed into it. In our example with the Knight, it has a length of 8. The tile_inst variable is set to whatever tile is at the matching coordinates, and then runs our two check functions to verify if the tile is valid or not.
The is_valid_tile function checks to make sure a tile exists at the coordinates we input, and that the tile is empty. The has_enemy_piece function checks to make sure a tile exists and is NOT empty, and if that's true it checks of the piece on that tile matches the piece that's moving.
After it passes one of the validity checks, it splits based on if the piece is a pawn or not. The reason we have a special case for the pawn is because it only takes pieces diagonally, so if we did the same check as the other pieces then the pawn would only take pieces in front of it.
The mark_path function is what is used instead for the pieces with free range of movement. Rather than setting an array of coordinates, each call of the function sets which direction to check movement in.
Here is what each parameter does:
- row: The row coordinate the piece is on
- col: The column coordinate the piece is on
- row_offset: What direction is being checked vertically (up/down)
- col_ofset: What direction is being checked horizontally (left/right)
- player: Who the piece belongs to
The tile_inst variable is first defined to be empty. Then we move the coordinate we're checking up/down and left/right based on our offsets. We set tile_inst to whatever tile matches those coordinates, and then mark it as valid similarly to how mark_tiles does. However, the big difference with mark_path is that there's no set number of tiles in each direction, the piece can move until it gets blocked by something. So, the function will loop until we either hit an edge or find a tile with a piece on it. Crucially, we still mark the first tile with an enemy piece on it as valid so the player can take the opponent's piece, but we stop the loop afterwards since they shouldn't be able to take pieces behind it.
After the switch-case is done, the global.selected_piece variable is set to whatever piece the player selected for use in the Left Pressed event.
Although the mark_valid_tiles function may appear daunting at first, its purpose is to make debugging easier by having a straightforward path one can look at when trying to fix any issues or keep track of any variables they may need.
Wiki
User Stories
Design
Requirements
Architecture
Considerations & Issues
Documentation
- How to Add a Card to the Game
- How the Music Controller & Script work
- Card Paging & Search System (CardManager)
- How to Spawn Cards in a Room
- How the Chessboard works
- How to Add a New Board Color
- How to use Alert System
- Chess Logic
- Cheat Mode
- Timer
- How the new card system works
- How the NEW Chessboard works