1 /*
2 * tetristabitem.cpp
3 *
4 * Copyright 2009 David Vachulka <arch_dvx@users.sourceforge.net>
5 * Copyright (C) 2007-2008 Graeme Gott <graeme@gottcode.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
20 * MA 02110-1301, USA.
21 */
22
23 #include "tetristabitem.h"
24 #include "config.h"
25 #include "i18n.h"
26
27 static const Cell types[][4] = {
28 { Cell(0,0), Cell(0,1), Cell(0,2), Cell(0,3) },
29 { Cell(0,0), Cell(0,1), Cell(1,1), Cell(1,2) },
30 { Cell(1,0), Cell(0,1), Cell(1,1), Cell(0,2) },
31 { Cell(0,0), Cell(0,1), Cell(1,1), Cell(0,2) },
32 { Cell(0,0), Cell(1,0), Cell(1,1), Cell(1,2) },
33 { Cell(0,0), Cell(1,0), Cell(0,1), Cell(0,2) },
34 { Cell(0,0), Cell(1,0), Cell(0,1), Cell(1,1) }
35 };
36
37 static FXColor colors[] = {
38 FXRGB(196, 160, 0),
39 FXRGB(206, 92, 0),
40 FXRGB(143, 89, 2),
41 FXRGB(78, 154, 6),
42 FXRGB(32, 74, 135),
43 FXRGB(117, 80, 123),
44 FXRGB(164, 0, 0)
45 };
46
47 static const FXint timeout = 333;
48 static const FXint basicscore = 13;
49
Piece(FXint type,TetrisTabItem * parent)50 Piece::Piece(FXint type, TetrisTabItem* parent)
51 : m_type(type), m_parent(parent), m_pivot(4,1), m_valid(FALSE)
52 {
53 FXASSERT(type > 0 && type < 8);
54 Cell position[4];
55 cells(position, type);
56 for(int i = 0; i < 4; ++i) position[i].x += 5;
57 if(updatePosition(position)) m_valid = TRUE;
58 }
59
rotate()60 FXbool Piece::rotate()
61 {
62 Cell rotated[4];
63 for(int i = 0; i < 4; ++i)
64 {
65 FXint x = static_cast<FXint>(m_cells[i].x) - m_pivot.x;
66 FXint y = static_cast<FXint>(m_cells[i].y) - m_pivot.y;
67 rotated[i].x = y + m_pivot.x;
68 rotated[i].y = -x + m_pivot.y;
69 if(rotated[i].x > 9 || rotated[i].x < 0 || rotated[i].y > 19 || rotated[i].y < 0) return FALSE;
70 }
71 return updatePosition(rotated);
72 }
73
drop()74 void Piece::drop()
75 {
76 for(FXint i = 0; i < rows; ++i) moveDown();
77 }
78
cells(Cell * cells,FXint type)79 void Piece::cells(Cell* cells, FXint type)
80 {
81 FXASSERT(cells != 0);
82 FXASSERT(type > 0 && type < 8);
83 const Cell* values = types[type - 1];
84 for(FXint i = 0; i < 4; ++i) cells[i] = values[i];
85 }
86
move(FXint x,FXint y)87 FXbool Piece::move(FXint x, FXint y)
88 {
89 // Move cells
90 Cell moved[4];
91 for(int i = 0; i < 4; ++i)
92 {
93 moved[i].x = m_cells[i].x + x;
94 moved[i].y = m_cells[i].y + y;
95 if(moved[i].x > columns-1 || moved[i].x < 0 || moved[i].y > rows-1 || moved[i].y < 0) return FALSE;
96 }
97
98 FXbool success = updatePosition(moved);
99 if (success)
100 {
101 m_pivot.x += x;
102 m_pivot.y += y;
103 }
104 return success;
105 }
106
updatePosition(const Cell * ucells)107 FXbool Piece::updatePosition(const Cell* ucells)
108 {
109 // Check for collision of cells
110 const Cell* cell = 0;
111 FXbool solid = FALSE;
112 for(int i = 0; i < 4; ++i)
113 {
114 cell = &ucells[i];
115 solid = m_parent->cell(cell->x, cell->y);
116 if(solid)
117 {
118 for(int j = 0; j < 4; ++j)
119 {
120 if(*cell == m_cells[j]) solid = FALSE;
121 }
122 }
123 if(solid) return FALSE;
124 }
125
126 // Move cells
127 if(m_cells[0].x != -1)
128 {
129 for(int i = 0; i < 4; ++i)
130 {
131 m_parent->removeCell(m_cells[i].x, m_cells[i].y);
132 }
133 }
134 for(int i = 0; i < 4; ++i)
135 {
136 m_parent->addCell(ucells[i].x, ucells[i].y, m_type);
137 }
138 for(int i = 0; i < 4; ++i)
139 {
140 m_cells[i] = ucells[i];
141 }
142
143 return TRUE;
144 }
145
146 FXDEFMAP(TetrisTabItem) TetrisTabItemMap[] = {
147 FXMAPFUNC(SEL_PAINT, TetrisTabItem_GAMECANVAS, TetrisTabItem::onPaint),
148 FXMAPFUNC(SEL_PAINT, TetrisTabItem_NEXTCANVAS, TetrisTabItem::onPaint),
149 FXMAPFUNC(SEL_COMMAND, TetrisTabItem_NEW, TetrisTabItem::onNewGame),
150 FXMAPFUNC(SEL_COMMAND, TetrisTabItem_PAUSE, TetrisTabItem::onPauseGame),
151 FXMAPFUNC(SEL_TIMEOUT, TetrisTabItem_TETRISTIMEOUT, TetrisTabItem::onTimeout)
152 };
153
FXIMPLEMENT(TetrisTabItem,dxEXTabItem,TetrisTabItemMap,ARRAYNUMBER (TetrisTabItemMap))154 FXIMPLEMENT(TetrisTabItem, dxEXTabItem, TetrisTabItemMap, ARRAYNUMBER(TetrisTabItemMap))
155
156 TetrisTabItem::TetrisTabItem(dxTabBook *tab, const FXString &tabtext, FXIcon *ic=0, FXuint opts=TAB_TOP_NORMAL, FXint id=0)
157 : dxEXTabItem(tab, tabtext, ic, opts),
158 m_parent(tab),
159 m_apiece(0),
160 m_anext(0),
161 m_removedLines(0),
162 m_level(1),
163 m_score(0),
164 m_nextPiece(0),
165 m_id(id),
166 m_paused(FALSE),
167 m_done(FALSE),
168 m_pauseEnable(FALSE),
169 m_piece(0),
170 m_penColor(fxcolorfromname("black"))
171 {
172 m_mainframe = new FXVerticalFrame(m_parent, FRAME_RAISED|LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y);
173
174 m_splitter = new FXSplitter(m_mainframe, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y|SPLITTER_REVERSED|SPLITTER_TRACKING);
175
176 m_gameframe = new FXVerticalFrame(m_splitter, LAYOUT_FILL_X|LAYOUT_FILL_Y, 0, 0, 0, 0, 1, 1, 1, 1);
177 m_gamecanvas = new FXCanvas(m_gameframe, this, TetrisTabItem_GAMECANVAS, FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_FILL_Y);
178 m_gamecanvas->setFocus();
179
180 m_otherframe = new FXVerticalFrame(m_splitter, LAYOUT_FILL_X|LAYOUT_FILL_Y|LAYOUT_FIX_WIDTH, 0, 0, 0, 0, 1, 1, 1, 1);
181 new FXLabel(m_otherframe, _("Next piece:"));
182 m_nextcanvas = new FXCanvas(m_otherframe, this, TetrisTabItem_NEXTCANVAS, LAYOUT_LEFT|LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT);
183 m_nextcanvas->setWidth(40);
184 m_nextcanvas->setHeight(80);
185 m_levelLabel = new FXLabel(m_otherframe, FXStringFormat(_("Level: %d"), m_level));
186 m_scoreLabel = new FXLabel(m_otherframe, FXStringFormat(_("Score: %d"), m_score));
187 m_linesLabel = new FXLabel(m_otherframe, FXStringFormat(_("Lines: %d"), m_removedLines));
188 m_newButton = new dxEXButton(m_otherframe, _("&New game"), NULL, this, TetrisTabItem_NEW);
189 m_pauseButton = new dxEXButton(m_otherframe, _("&Pause game"), NULL, this, TetrisTabItem_PAUSE);
190 m_pauseButton->disable();
191
192 m_messageFont = new FXFont(getApp(), "helvetica", 25, FXFont::Bold, FXFont::Straight, FONTENCODING_DEFAULT, FXFont::NonExpanded, FXFont::Scalable|FXFont::Rotatable);
193
194 for(FXint x=0; x<columns; ++x)
195 {
196 for(FXint y=0; y<rows; ++y)
197 {
198 m_cells[x][y] = 0;
199 }
200 }
201 }
202
~TetrisTabItem()203 TetrisTabItem::~TetrisTabItem()
204 {
205 delete m_messageFont;
206 }
207
cell(FXint x,FXint y) const208 FXbool TetrisTabItem::cell(FXint x, FXint y) const
209 {
210 FXASSERT(x >= 0 && x < columns);
211 FXASSERT(y >= 0 && y < rows);
212 return m_cells[x][y] != 0;
213 }
214
addCell(FXint x,FXint y,FXint type)215 void TetrisTabItem::addCell(FXint x, FXint y, FXint type)
216 {
217 FXASSERT(x >= 0 && x < columns);
218 FXASSERT(y >= 0 && y < rows);
219 FXASSERT(type > 0 && type < 8);
220 FXASSERT(m_cells[x][y] == 0);
221 m_cells[x][y] = type;
222 updateCell(x,y);
223 }
224
removeCell(FXint x,FXint y,FXbool update)225 void TetrisTabItem::removeCell(FXint x, FXint y, FXbool update)
226 {
227 FXASSERT(x >= 0 && x < columns);
228 FXASSERT(y >= 0 && y < rows);
229 m_cells[x][y] = 0;
230 if(update) updateCell(x,y);
231 }
232
updateCell(FXint x,FXint y)233 void TetrisTabItem::updateCell(FXint x, FXint y)
234 {
235 FXASSERT(x >= 0 && x < columns);
236 FXASSERT(y >= 0 && y < rows);
237 FXDCWindow dc(m_gamecanvas);
238 if(m_cells[x][y] != 0)
239 dc.setForeground(colors[m_cells[x][y]-1]);
240 else
241 dc.setForeground(m_gamecanvas->getBackColor());
242 dc.fillRectangle(x*m_apiece+1, y*m_apiece+1, m_apiece-2, m_apiece-2);
243 }
244
findFullLines()245 void TetrisTabItem::findFullLines()
246 {
247 // Empty list of full lines
248 for(FXint i = 0; i < 4; ++i)
249 m_fullLines[i] = -1;
250 FXint pos = 0;
251 // Find full lines
252 FXbool full = FALSE;
253 for(FXint row = 0; row < rows; ++row)
254 {
255 full = TRUE;
256 for(FXint col = 0; col < columns; ++col)
257 {
258 if(m_cells[col][row] == 0) full = FALSE;
259 }
260 if(full)
261 {
262 m_fullLines[pos] = row;
263 ++pos;
264 }
265 }
266 }
267
createGeom()268 void TetrisTabItem::createGeom()
269 {
270 m_mainframe->create();
271 m_splitter->create();
272 m_gameframe->create();
273 m_gamecanvas->create();
274 m_otherframe->create();
275 m_nextcanvas->create();
276 m_levelLabel->create();
277 m_scoreLabel->create();
278 m_linesLabel->create();
279 m_messageFont->create();
280 }
281
setColor(IrcColor color)282 void TetrisTabItem::setColor(IrcColor color)
283 {
284 m_gamecanvas->setBackColor(color.back);
285 m_nextcanvas->setBackColor(color.back);
286 m_penColor = color.text;
287 }
288
setGameFocus()289 void TetrisTabItem::setGameFocus()
290 {
291 m_gamecanvas->setFocus();
292 }
293
newGame()294 void TetrisTabItem::newGame()
295 {
296 if(getApp()->hasTimeout(this, TetrisTabItem_TETRISTIMEOUT)) return;
297 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
298 delete m_piece;
299 m_piece = 0;
300 m_paused = FALSE;
301 m_done = FALSE;
302 m_newButton->disable();
303 m_pauseEnable = TRUE;
304 m_pauseButton->enable();
305 m_pauseButton->setText(_("&Pause game"));
306 m_removedLines = 0;
307 m_level = 1;
308 m_score = 0;
309 m_nextPiece = rand()%7+1;
310 for(FXint i=0; i<4; ++i)
311 m_fullLines[i] = -1;
312 for(int col = 0; col < columns; ++col)
313 {
314 for(int row = 0; row < rows; ++row)
315 {
316 m_cells[col][row] = 0;
317 }
318 }
319 createPiece();
320 redraw();
321 updateLabels();
322 }
323
stopGame()324 void TetrisTabItem::stopGame()
325 {
326 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
327 delete m_piece;
328 m_piece = 0;
329 m_paused = FALSE;
330 m_done = FALSE;
331 m_newButton->enable();
332 m_pauseEnable = FALSE;
333 m_pauseButton->disable();
334 m_removedLines = 0;
335 m_level = 1;
336 m_score = 0;
337 m_nextPiece = rand()%7+1;
338 for(FXint i=0; i<4; ++i)
339 m_fullLines[i] = -1;
340 for(int col = 0; col < columns; ++col)
341 {
342 for(int row = 0; row < rows; ++row)
343 {
344 m_cells[col][row] = 0;
345 }
346 }
347 }
348
reparentTab()349 void TetrisTabItem::reparentTab()
350 {
351 reparent(m_parent);
352 m_mainframe->reparent(m_parent);
353 recalc();
354 m_mainframe->recalc();
355 }
356
moveLeft()357 void TetrisTabItem::moveLeft()
358 {
359 if(m_done || m_paused) return;
360 if(!m_piece) return;
361 m_piece->moveLeft();
362 }
363
moveRight()364 void TetrisTabItem::moveRight()
365 {
366 if(m_done || m_paused) return;
367 if(!m_piece) return;
368 m_piece->moveRight();
369 }
370
rotate()371 void TetrisTabItem::rotate()
372 {
373 if(m_done || m_paused) return;
374 if(!m_piece) return;
375 m_piece->rotate();
376 }
377
drop()378 void TetrisTabItem::drop()
379 {
380 if(m_done || m_paused) return;
381 if(!m_piece) return;
382 m_piece->drop();
383 landPiece();
384 }
385
createPiece()386 void TetrisTabItem::createPiece()
387 {
388 FXASSERT(m_piece == 0);
389 m_piece = new Piece(m_nextPiece, this);
390 if(!m_piece->isValid())
391 {
392 delete m_piece;
393 m_piece = 0;
394 m_done = TRUE;
395 gameOver();
396 return;
397 }
398 m_nextPiece = rand()%7+1;
399 redraw();
400 getApp()->addTimeout(this, TetrisTabItem_TETRISTIMEOUT, timeout-(m_level-1)*13);
401 }
402
drawLines()403 void TetrisTabItem::drawLines()
404 {
405 FXDCWindow dc(m_gamecanvas);
406 dc.setForeground(m_gamecanvas->getBackColor());
407 dc.fillRectangle(0, 0, m_gamecanvas->getWidth(), m_gamecanvas->getHeight());
408 dc.setLineWidth(2);
409 dc.setForeground(m_penColor);
410 dc.drawLine(0,0,0,rows*m_apiece);
411 dc.drawLine(0,rows*m_apiece,columns*m_apiece,rows*m_apiece);
412 dc.drawLine(columns*m_apiece,rows*m_apiece,columns*m_apiece,0);
413 for(FXint x=0; x<columns; ++x)
414 {
415 for(FXint y=0; y<rows; ++y)
416 {
417 if(m_cells[x][y] != 0)
418 {
419 dc.setForeground(colors[m_cells[x][y]-1]);
420 dc.fillRectangle(x*m_apiece+1, y*m_apiece+1, m_apiece-2, m_apiece-2);
421 }
422 }
423 }
424 dc.setForeground(m_penColor);
425 if(!m_done && !m_piece)
426 {
427 dc.setFont(getApp()->getNormalFont());
428 dc.drawText(2, getApp()->getNormalFont()->getFontHeight()+2, _("Keys for playing:"));
429 dc.drawText(2, 2*(getApp()->getNormalFont()->getFontHeight()+2), _("n .. new game"));
430 dc.drawText(2, 3*(getApp()->getNormalFont()->getFontHeight()+2), _("p .. pause game"));
431 dc.drawText(2, 4*(getApp()->getNormalFont()->getFontHeight()+2), _("i .. rotate piece"));
432 dc.drawText(2, 5*(getApp()->getNormalFont()->getFontHeight()+2), _("l .. move piece right"));
433 dc.drawText(2, 6*(getApp()->getNormalFont()->getFontHeight()+2), _("k .. drop piece"));
434 dc.drawText(2, 7*(getApp()->getNormalFont()->getFontHeight()+2), _("j .. move piece left"));
435 }
436 dc.setFont(m_messageFont);
437 if(m_done)
438 {
439 FXint width = m_messageFont->getTextWidth(_("GAME OVER"));
440 FXint x = 2;
441 if(width < m_apiece*columns-4) x = (m_apiece*columns-width)/2;
442 dc.drawText(x, m_gamecanvas->getHeight()/2, _("GAME OVER"));
443 }
444 if(m_paused)
445 {
446 FXint width = m_messageFont->getTextWidth(_("PAUSED"));
447 FXint x = 2;
448 if(width < m_apiece*columns-4) x = (m_apiece*columns-width)/2;
449 dc.drawText(x, m_gamecanvas->getHeight()/2, _("PAUSED"));
450 }
451 }
452
drawNextPiece(FXint type)453 void TetrisTabItem::drawNextPiece(FXint type)
454 {
455 if(type<1 || type>7) return;
456 FXDCWindow dc(m_nextcanvas);
457 dc.setForeground(m_nextcanvas->getBackColor());
458 dc.fillRectangle(0, 0, m_nextcanvas->getWidth(), m_nextcanvas->getHeight());
459 for(FXint i=0; i<4; ++i)
460 {
461 dc.setForeground(colors[type-1]);
462 dc.fillRectangle(types[type - 1][i].x*m_anext+1, types[type - 1][i].y*m_anext+1, m_anext-2, m_anext-2);
463 }
464 }
465
removeLines()466 void TetrisTabItem::removeLines()
467 {
468 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
469 FXint deltascore = basicscore*m_level;
470 // Loop through full lines
471 for(FXint i = 0; i < 4; ++i)
472 {
473 FXint row = m_fullLines[i];
474 if(row == -1)
475 break;
476 // Remove line
477 for(FXint col = 0; col < 10; ++col)
478 {
479 removeCell(col, row, FALSE); //not need update canvas, CreatePiece do it
480 }
481 ++m_removedLines;
482 deltascore *= 3;
483 // Shift board down
484 for(; row > 0; --row)
485 {
486 for(FXint col = 0; col < columns; ++col)
487 {
488 m_cells[col][row] = m_cells[col][row - 1];
489 }
490 }
491 }
492 // Remove top line
493 if(m_fullLines[0] != -1)
494 {
495 for(FXint col = 0; col < 10; ++col)
496 {
497 removeCell(col, 0, FALSE); //not need update canvas, CreatePiece do it
498 }
499 }
500 m_level = (m_removedLines / 10) + 1;
501 m_score += deltascore;
502 // Empty list of full lines
503 for(FXint i = 0; i < 4; ++i)
504 m_fullLines[i] = -1;
505 updateLabels();
506 // Add new piece
507 createPiece();
508 }
509
landPiece()510 void TetrisTabItem::landPiece()
511 {
512 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
513 delete m_piece;
514 m_piece = 0;
515 findFullLines();
516 if(m_fullLines[0] != -1) removeLines();
517 else createPiece();
518 }
519
pauseResumeGame()520 void TetrisTabItem::pauseResumeGame()
521 {
522 if(m_done) return;
523 if(!m_pauseEnable) return;
524 if(m_paused)
525 {
526 getApp()->addTimeout(this, TetrisTabItem_TETRISTIMEOUT, timeout-(m_level-1)*13);
527 m_pauseButton->setText(_("&Pause game"));
528 }
529 else
530 {
531 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
532 m_newButton->enable();
533 m_pauseButton->setText(_("&Resume game"));
534 }
535 m_paused = !m_paused;
536 redraw();
537 }
538
gameOver()539 void TetrisTabItem::gameOver()
540 {
541 getApp()->removeTimeout(this, TetrisTabItem_TETRISTIMEOUT);
542 m_done = TRUE;
543 m_newButton->enable();
544 m_pauseEnable = FALSE;
545 m_pauseButton->disable();
546 redraw();
547 }
548
updateLabels()549 void TetrisTabItem::updateLabels()
550 {
551 m_levelLabel->setText(FXStringFormat(_("Level: %d"), m_level));
552 m_scoreLabel->setText(FXStringFormat(_("Score: %d"), m_score));
553 m_linesLabel->setText(FXStringFormat(_("Lines: %d"), m_removedLines));
554 }
555
redraw()556 void TetrisTabItem::redraw()
557 {
558 if(m_gamecanvas)
559 {
560 m_apiece = FXMIN(m_gamecanvas->getWidth()/columns, m_gamecanvas->getHeight()/rows);
561 drawLines();
562 }
563 if(m_nextcanvas)
564 {
565 m_anext = FXMIN(m_nextcanvas->getWidth()/2, m_nextcanvas->getHeight()/4);
566 drawNextPiece(m_nextPiece);
567 }
568 }
569
onPaint(FXObject * sender,FXSelector,void * ptr)570 long TetrisTabItem::onPaint(FXObject *sender, FXSelector, void *ptr)
571 {
572 FXEvent *ev=(FXEvent*)ptr;
573 FXDCWindow dc((FXCanvas*)sender,ev);
574 dc.setForeground(((FXCanvas*)sender)->getBackColor());
575 dc.fillRectangle(ev->rect.x,ev->rect.y,ev->rect.w,ev->rect.h);
576 redraw();
577 return 1;
578 }
579
onTimeout(FXObject *,FXSelector,void *)580 long TetrisTabItem::onTimeout(FXObject*, FXSelector, void*)
581 {
582 FXASSERT(m_piece != 0);
583 if(m_piece->moveDown())
584 {
585 getApp()->addTimeout(this, TetrisTabItem_TETRISTIMEOUT, timeout-(m_level-1)*13);
586 }
587 else landPiece();
588 return 1;
589 }
590
onNewGame(FXObject *,FXSelector,void *)591 long TetrisTabItem::onNewGame(FXObject*, FXSelector, void*)
592 {
593 newGame();
594 return 1;
595 }
596
onPauseGame(FXObject *,FXSelector,void *)597 long TetrisTabItem::onPauseGame(FXObject*, FXSelector, void*)
598 {
599 pauseResumeGame();
600 return 1;
601 }
602
603