Nugget
playfield.hh
1 /*
2 
3 MIT License
4 
5 Copyright (c) 2022 PCSX-Redux authors
6 
7 Permission is hereby granted, free of charge, to any person obtaining a copy
8 of this software and associated documentation files (the "Software"), to deal
9 in the Software without restriction, including without limitation the rights
10 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 copies of the Software, and to permit persons to whom the Software is
12 furnished to do so, subject to the following conditions:
13 
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
16 
17 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 SOFTWARE.
24 
25 */
26 
27 #pragma once
28 
29 #include <stdint.h>
30 
31 #include "constants.hh"
32 #include "pieces.hh"
33 #include "psyqo/fragments.hh"
34 #include "psyqo/gpu.hh"
35 #include "psyqo/primitives/rectangles.hh"
36 #include "rand.hh"
37 
38 // The Playfield class will be responsible for holding the basic state
39 // of the playfield, and drawing it to the screen. It's technically
40 // variable sized, at compilation time, using the template arguments.
41 // The default Tetris configuration is 10 columns and 20 rows.
42 template <size_t COLUMNS, size_t ROWS>
43 struct Playfield {
44  static constexpr size_t Columns = COLUMNS;
45  static constexpr size_t Rows = ROWS;
46 
47  private:
48  enum CellType : uint8_t { Empty, Cyan, Yellow, Purple, Blue, Orange, Red, Green };
49  static constexpr unsigned BLOCK_SIZE = 10;
50  static constexpr unsigned SCREEN_WIDTH = 320;
51  static constexpr unsigned SCREEN_HEIGHT = 240;
52  static constexpr unsigned LEFT = SCREEN_WIDTH / 2 - (BLOCK_SIZE * COLUMNS / 2);
53  static constexpr unsigned TOP = SCREEN_HEIGHT / 2 - (BLOCK_SIZE * ROWS / 2);
54 
55  // First, we will have our game logic methods.
56  public:
57  // Empties the playfield. This needs to be called at initialization time only,
58  // as it touches the fragments. This is the only method that is mutating the
59  // fragments implicitly.
60  void clear() {
61  for (size_t x = 0; x < COLUMNS; x++) {
62  for (size_t y = 0; y < ROWS; y++) {
63  m_cells[x][y] = Empty;
64  }
65  }
66  m_fieldFragment.count = 0;
67  m_blockFragment.count = 0;
68  }
69 
70  // Fills the playfield with random blocks.
71  void fillRandom(Rand& rand) {
72  for (size_t x = 0; x < COLUMNS; x++) {
73  for (size_t y = 0; y < ROWS; y++) {
74  m_cells[x][y] = CellType(rand.rand<7>() + 1);
75  }
76  }
77  }
78 
79  // Returns true if the given block collisions with anything at the given position.
80  bool collision(unsigned block, int x, int y, unsigned rotation) {
81  for (size_t i = 0; i < 4; i++) {
82  for (size_t j = 0; j < 4; j++) {
83  if (PIECES[block - 1][rotation][j][i]) {
84  int xx = x + i;
85  int yy = y + j;
86  if ((xx < 0) || (yy < 0) || (xx >= COLUMNS) || (yy >= ROWS) || (m_cells[xx][yy] != Empty)) {
87  return true;
88  }
89  }
90  }
91  }
92  return false;
93  }
94 
95  // Cements a block into the playfield.
96  void place(unsigned block, int x, int y, unsigned rotation) {
97  for (size_t i = 0; i < 4; i++) {
98  for (size_t j = 0; j < 4; j++) {
99  if (PIECES[block - 1][rotation][j][i]) {
100  m_cells[x + i][y + j] = CellType(block);
101  }
102  }
103  }
104  }
105 
106  // Computes the amount of lines that are complete, and remove them.
107  // Returns the number of removed lines.
108  int removeLines() {
109  int lines = 0;
110  for (size_t y = 0; y < ROWS; y++) {
111  bool full = true;
112  for (size_t x = 0; x < COLUMNS; x++) {
113  if (m_cells[x][y] == Empty) {
114  full = false;
115  break;
116  }
117  }
118  if (full) {
119  for (size_t x = 0; x < COLUMNS; x++) {
120  m_cells[x][y] = Empty;
121  }
122  for (size_t yy = y; yy > 0; yy--) {
123  for (size_t x = 0; x < COLUMNS; x++) {
124  m_cells[x][yy] = m_cells[x][yy - 1];
125  }
126  }
127  y--;
128  lines++;
129  }
130  }
131  return lines;
132  }
133 
134  // Then, the drawing-related methods.
135  private:
136  // The playfield fragment contains a fixed prologue of two rectangles
137  // to draw the background and exterior.
138  struct Prologue {
139  psyqo::Prim::Rectangle outerField;
140  psyqo::Prim::Rectangle innerField;
141  };
142  // This represents one of the blocks drawn in the playfield.
143  struct Block {
146  };
147 
148  // We are using a `FixedFragmentWithPrologue` to store the playfield Fragment.
149  // We have a maximum of `ROWS * COLUMNS` blocks to display, so that's the
150  // maximu amount of elements we need in our fragment.
152  // The current block fragment is a fixed fragment with a maximum of 4 blocks.
153  // There is a subtle difference in the way we're drawing the current tetromino
154  // blocks, compared to the blocks on the playfield. The current block fragment
155  // won't have contours, to try and symbolize a sort of cement that's added when
156  // a tetromino is fused in the playfield.
158 
159  // The pause screen will hide the current playfield and block, setting the
160  // fragments counts to 0, so we need to keep track of the previous values
161  // to simply re-assign them when we're being unpaused.
162  unsigned m_oldFieldFragmentCount;
163  unsigned m_oldBlockFragmentCount;
164 
165  public:
166  // In our constructor, we're initializing the pieces of the fragments
167  // that will never change throughout the lifetime of the game.
168  Playfield() {
169  m_fieldFragment.prologue.outerField.position.x = LEFT - 2;
170  m_fieldFragment.prologue.outerField.position.y = TOP;
171  m_fieldFragment.prologue.outerField.size.x = BLOCK_SIZE * COLUMNS + 4;
172  m_fieldFragment.prologue.outerField.size.y = BLOCK_SIZE * ROWS + 2;
173  m_fieldFragment.prologue.outerField.setColor(DARKERGREY);
174  m_fieldFragment.prologue.innerField.position.x = LEFT;
175  m_fieldFragment.prologue.innerField.position.y = TOP;
176  m_fieldFragment.prologue.innerField.size.x = BLOCK_SIZE * COLUMNS;
177  m_fieldFragment.prologue.innerField.size.y = BLOCK_SIZE * ROWS;
178  m_fieldFragment.prologue.innerField.setColor(DARKGREY);
179  for (auto& block : m_fieldFragment.primitives) {
180  block.outer.size.w = block.outer.size.h = BLOCK_SIZE;
181  }
182  clear();
183  }
184 
185  // This method is called when the game is paused. It simply empties
186  // the fragments to hide the blocks.
187  void emptyFragments() {
188  m_oldFieldFragmentCount = m_fieldFragment.count;
189  m_oldBlockFragmentCount = m_blockFragment.count;
190  m_fieldFragment.count = 0;
191  m_blockFragment.count = 0;
192  }
193 
194  // This method is called when the game is unpaused. It simply re-assigns
195  // the fragments to their previous values so everything is being
196  // displayed again.
197  void restoreFragments() {
198  m_fieldFragment.count = m_oldFieldFragmentCount;
199  m_blockFragment.count = m_oldBlockFragmentCount;
200  }
201 
202  // This method needs to be called after the playfield has been changed in
203  // any way. It will mutate the fragments to reflect the new playfield,
204  // so it is not safe to call from a callback, but only from the `::frame()`
205  // method of a scene.
206  void updateFieldFragment() {
207  unsigned counter = 0;
208  // We simply walk over the whole playfield, and update the fragment
209  // accordingly. The will count the number of actual blocks being
210  // displayed in our fragment to assign it at the end of the loops.
211  for (size_t x = 0; x < COLUMNS; ++x) {
212  for (size_t y = 0; y < ROWS; ++y) {
213  bool addme = true;
214  auto& block = m_fieldFragment.primitives[counter];
215  switch (m_cells[x][y]) {
216  case Empty:
217  addme = false;
218  break;
219  case Red:
220  block.outer.setColor(RED);
221  block.inner.setColor(HIRED);
222  break;
223  case Orange:
224  block.outer.setColor(ORANGE);
225  block.inner.setColor(HIORANGE);
226  break;
227  case Yellow:
228  block.outer.setColor(YELLOW);
229  block.inner.setColor(HIYELLOW);
230  break;
231  case Green:
232  block.outer.setColor(GREEN);
233  block.inner.setColor(HIGREEN);
234  break;
235  case Blue:
236  block.outer.setColor(BLUE);
237  block.inner.setColor(HIBLUE);
238  break;
239  case Cyan:
240  block.outer.setColor(CYAN);
241  block.inner.setColor(HICYAN);
242  break;
243  case Purple:
244  block.outer.setColor(PURPLE);
245  block.inner.setColor(HIPURPLE);
246  break;
247  }
248  if (addme) {
249  block.outer.position.x = LEFT + x * BLOCK_SIZE;
250  block.outer.position.y = TOP + y * BLOCK_SIZE;
251  block.inner.position.x = LEFT + x * BLOCK_SIZE + 1;
252  block.inner.position.y = TOP + y * BLOCK_SIZE + 1;
253  counter++;
254  }
255  }
256  }
257  m_fieldFragment.count = counter;
258  }
259 
260  // This method needs to be called when the current tetromino has
261  // changed in any way. This will mutate the fragments to reflect the
262  // new current tetromino, so it is not safe to call from a callback,
263  // but only from the `::frame()` method of a scene.
264  void updateBlockFragment(unsigned block, unsigned x, unsigned y, unsigned rotation) {
265  if (block == 0) {
266  m_blockFragment.count = 0;
267  return;
268  }
269  unsigned counter = 0;
270  for (size_t i = 0; i < 4; i++) {
271  for (size_t j = 0; j < 4; j++) {
272  if (PIECES[block - 1][rotation][j][i]) {
273  auto& blockFragment = m_blockFragment.primitives[counter];
274  blockFragment.position.x = LEFT + x * BLOCK_SIZE + i * BLOCK_SIZE + 1;
275  blockFragment.position.y = TOP + y * BLOCK_SIZE + j * BLOCK_SIZE + 1;
276  blockFragment.setColor(PIECES_HICOLORS[block - 1]);
277  counter++;
278  }
279  }
280  }
281  m_blockFragment.count = counter;
282  }
283 
284  // This method will render the playfield and the current tetromino.
285  // For now it sends the two fragments directly to the GPU, but
286  // this will be updated to use a DMA chain instead later on.
287  void render(psyqo::GPU& gpu) {
288  gpu.sendFragment(m_fieldFragment);
289  gpu.sendFragment(m_blockFragment);
290  }
291 
292  private:
293  CellType m_cells[COLUMNS][ROWS];
294 };
Definition: rand.hh:33
The singleton GPU class.
Definition: gpu.hh:57
void sendFragment(const Fragment &fragment)
Immediately sends a fragment to the GPU. This is a blocking operation. See the fragments....
Definition: gpu.hh:166
Definition: playfield.hh:43
The 8x8 Rectangle primitive.
Definition: rectangles.hh:115
The Rectangle primitive.
Definition: rectangles.hh:46