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