You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kreversi/board.cpp

577 lines
14 KiB

/* Yo Emacs, this -*- C++ -*-
*******************************************************************
*******************************************************************
*
*
* KREVERSI
*
*
*******************************************************************
*
* A Reversi (or sometimes called Othello) game
*
*******************************************************************
*
* created 1997 by Mario Weilguni <mweilguni@sime.com>
*
*******************************************************************
*
* This file is part of the KDE project "KREVERSI"
*
* KREVERSI is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* KREVERSI is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KREVERSI; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*******************************************************************
*/
#include <unistd.h>
#include <qpainter.h>
#include <qfont.h>
#include <kapplication.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#include <knotifyclient.h>
#include <klocale.h>
#include <kexthighscore.h>
#include <kdebug.h>
#include "board.h"
#include "prefs.h"
#include "Engine.h"
#include "qreversigame.h"
#ifndef PICDATA
#define PICDATA(x) KGlobal::dirs()->findResource("appdata", QString("pics/")+ x)
#endif
const uint HINT_BLINKRATE = 250000;
const uint ANIMATION_DELAY = 3000;
const uint CHIP_OFFSET[NbColors] = { 24, 1 };
const uint CHIP_SIZE = 36;
#define OFFSET() (zoomedSize() * 3 / 4)
// ================================================================
// class KReversiBoardView
QReversiBoardView::QReversiBoardView(QWidget *parent, QReversiGame *krgame)
: QWidget(parent, "board"),
chiptype(Unloaded)
{
m_krgame = krgame;
m_marksShowing = true;
m_legalMovesShowing = false;
m_legalMoves.clear();
m_showLastMove = false;
m_lastMoveShown = SimpleMove();
}
QReversiBoardView::~QReversiBoardView()
{
}
// ----------------------------------------------------------------
// Start it all up.
//
void QReversiBoardView::start()
{
updateBoard(true);
adjustSize();
}
void QReversiBoardView::loadChips(ChipType type)
{
QString name("pics/");
name += (type==Colored ? "chips.png" : "chips_mono.png");
QString s = KGlobal::dirs()->findResource("appdata", name);
bool ok = allchips.load(s);
Q_ASSERT( ok && allchips.width()==CHIP_SIZE*5
&& allchips.height()==CHIP_SIZE*5 );
chiptype = type;
update();
}
// Negative speed is allowed. If speed is negative,
// no animations are displayed.
//
void QReversiBoardView::setAnimationSpeed(uint speed)
{
if (speed <= 10)
anim_speed = speed;
}
// Handle mouse clicks.
//
void QReversiBoardView::mousePressEvent(QMouseEvent *e)
{
// Only handle left button. No context menu.
if ( e->button() != LeftButton ) {
e->ignore();
return;
}
int offset = m_marksShowing ? OFFSET() : 0;
int px = e->pos().x()- 1 - offset;
int py = e->pos().y()- 1 - offset;
if (px < 0 || px >= 8 * (int) zoomedSize()
|| py < 0 || py >= 8 * (int) zoomedSize()) {
e->ignore();
return;
}
emit signalSquareClicked(py / zoomedSize(), px / zoomedSize());
}
void QReversiBoardView::showHint(Move move)
{
// Only show a hint if there is a move to show.
if (move.x() == -1)
return;
// Blink with a piece at the location where the hint move points.
//
// The isVisible condition has been added so that when the player
// was viewing a hint and quits the game window, the game doesn't
// still have to do all this looping and directly ends.
QPainter p(this);
p.setPen(black);
m_hintShowing = true;
for (int flash = 0;
flash < 100 && m_hintShowing && isVisible();
flash++)
{
if (flash & 1) {
// FIXME: Draw small circle if showLegalMoves is turned on.
drawPiece(move.y() - 1, move.x() - 1, Nobody);
if (m_legalMovesShowing)
drawSmallCircle(move.x(), move.y(), p);
}
else
drawPiece(move.y() - 1, move.x() - 1, m_krgame->toMove());
// keep GUI alive while waiting
for (int dummy = 0; dummy < 5; dummy++) {
usleep(HINT_BLINKRATE / 5);
qApp->processEvents();
}
}
m_hintShowing = false;
// Draw the empty square again.
drawPiece(move.y() - 1, move.x() - 1, m_krgame->color(move.x(), move.y()));
if (m_legalMovesShowing)
drawSmallCircle(move.x(), move.y(), p);
}
// Set the member m_hintShowing to false. This will make showHint()
// end, if it is running.
//
void QReversiBoardView::quitHint()
{
m_hintShowing = false;
}
void QReversiBoardView::setShowLegalMoves(bool show)
{
m_legalMovesShowing = show;
updateBoard(true);
}
void QReversiBoardView::setShowMarks(bool show)
{
m_marksShowing = show;
updateBoard(true);
}
void QReversiBoardView::setShowLastMove(bool show)
{
m_showLastMove = show;
updateBoard(true);
}
// ================================================================
// Functions related to drawing/painting
// Flash all pieces which are turned.
//
// NOTE: This code is quite a hack. Should make it better.
//
void QReversiBoardView::animateChanged(Move move)
{
if (anim_speed == 0)
return;
// Draw the new piece.
drawPiece(move.y() - 1, move.x() - 1, move.color());
// Animate row by row in all directions.
for (int dx = -1; dx < 2; dx++)
for (int dy = -1; dy < 2; dy++)
if ((dx != 0) || (dy != 0))
animateChangedRow(move.y() - 1, move.x() - 1, dy, dx);
}
bool QReversiBoardView::isField(int row, int col) const
{
return ((0 <= row) && (row < 8) && (0 <= col) && (col < 8));
}
void QReversiBoardView::animateChangedRow(int row, int col, int dy, int dx)
{
row = row + dy;
col = col + dx;
while (isField(row, col)) {
if (m_krgame->wasTurned(col+1, row+1)) {
KNotifyClient::event(winId(), "click", i18n("Click"));
rotateChip(row, col);
} else
return;
col += dx;
row += dy;
}
}
void QReversiBoardView::rotateChip(uint row, uint col)
{
// Check which direction the chip has to be rotated. If the new
// chip is white, the chip was black first, so lets begin at index
// 1, otherwise it was white.
Color color = m_krgame->color(col+1, row+1);
uint from = CHIP_OFFSET[opponent(color)];
uint end = CHIP_OFFSET[color];
int delta = (color==White ? 1 : -1);
from += delta;
end -= delta;
for (uint i = from; i != end; i += delta) {
drawOnePiece(row, col, i);
kapp->flushX(); // FIXME: use QCanvas to avoid flicker...
usleep(ANIMATION_DELAY * anim_speed);
}
}
// Redraw the board. If 'force' is true, redraw everything, otherwise
// only redraw those squares that have changed (marked by
// m_krgame->squareModified(col, row)).
//
void QReversiBoardView::updateBoard (bool force)
{
QPainter p(this);
p.setPen(black);
// If we are showing legal moves, we have to erase the old ones
// before we can show the new ones. The easiest way to do that is
// to repaint everything.
//
// FIXME: A better way, perhaps, is to do the repainting in
// drawPiece (which should be renamed drawSquare).
if (m_legalMovesShowing)
force = true;
// Draw the squares of the board.
for (uint row = 0; row < 8; row++)
for (uint col = 0; col < 8; col++)
if ( force || m_krgame->squareModified(col + 1, row + 1) ) {
Color color = m_krgame->color(col + 1, row + 1);
drawPiece(row, col, color);
}
// Draw a border around the board.
int offset = m_marksShowing ? OFFSET() : 0;
p.drawRect(0 + offset, 0 + offset,
8 * zoomedSize() + 2, 8 * zoomedSize() + 2);
// Draw letters and numbers if appropriate.
if (m_marksShowing) {
QFont font("Sans Serif", zoomedSize() / 2 - 6);
font.setWeight(QFont::DemiBold);
QFontMetrics metrics(font);
p.setFont(font);
uint charHeight = metrics.ascent();
for (uint i = 0; i < 8; i++) {
QChar letter = "ABCDEFGH"[i];
QChar number = "12345678"[i];
uint charWidth = metrics.charWidth("ABCDEFGH", i);
// The horizontal letters
p.drawText(offset + i * zoomedSize() + (zoomedSize() - charWidth) / 2,
offset - charHeight / 2 + 2,
QString(letter));
p.drawText(offset + i * zoomedSize() + (zoomedSize() - charWidth) / 2,
offset + 8 * zoomedSize() + offset - charHeight / 2 + 2,
QString(letter));
// The vertical numbers
p.drawText((offset - charWidth) / 2 + 2,
offset + (i + 1) * zoomedSize() - charHeight / 2 + 2,
QString(number));
p.drawText(offset + 8 * zoomedSize() + (offset - charWidth) / 2 + 2,
offset + (i + 1) * zoomedSize() - charHeight / 2 + 2,
QString(number));
}
}
// Show legal moves.
if (m_legalMovesShowing)
showLegalMoves();
// Show last move
int ellipseSize = zoomedSize() / 3;
SimpleMove lastMove = m_krgame->lastMove();
if (m_showLastMove && lastMove.x() != -1) {
// Remove the last shown last move.
int col = m_lastMoveShown.x();
int row = m_lastMoveShown.y();
if (col != -1 && row != -1) {
if (lastMove.x() != col || lastMove.y() != row) {
//kdDebug() << "Redrawing piece at [" << col << "," << row
// << "] with color " << m_krgame->color(col, row)
// << endl;
drawPiece(row - 1, col - 1, m_krgame->color(col, row));
}
}
p.setPen(yellow);
p.setBackgroundColor(yellow);
p.setBrush(SolidPattern);
//kdDebug() << "Marking last move at ["
// << lastMove.x() << "," << lastMove.y() << "]"
// << endl;
int px = offset + (lastMove.x() - 1) * zoomedSize() + zoomedSize() / 2;
int py = offset + (lastMove.y() - 1) * zoomedSize() + zoomedSize() / 2;
p.drawEllipse(px - ellipseSize / 2 + 1, py - ellipseSize / 2 + 1,
ellipseSize - 1, ellipseSize - 1);
m_lastMoveShown = lastMove;
p.setPen(black);
p.setBackgroundColor(black);
p.setBrush(NoBrush);
}
}
// Show legal moves on the board.
void QReversiBoardView::showLegalMoves()
{
QPainter p(this);
p.setPen(black);
// Get the legal moves in the current position.
Color toMove = m_krgame->toMove();
MoveList moves = m_krgame->position().generateMoves(toMove);
// Show the moves on the board.
MoveList::iterator it;
for (it = moves.begin(); it != moves.end(); ++it)
drawSmallCircle((*it).x(), (*it).y(), p);
}
void QReversiBoardView::drawSmallCircle(int x, int y, QPainter &p)
{
int offset = m_marksShowing ? OFFSET() : 0;
int ellipseSize = zoomedSize() / 3;
int px = offset + (x - 1) * zoomedSize() + zoomedSize() / 2;
int py = offset + (y - 1) * zoomedSize() + zoomedSize() / 2;
p.drawEllipse(px - ellipseSize / 2, py - ellipseSize / 2,
ellipseSize, ellipseSize);
}
QPixmap QReversiBoardView::chipPixmap(Color color, uint size) const
{
return chipPixmap(CHIP_OFFSET[color], size);
}
// Get a pixmap for the chip 'i' at size 'size'.
//
QPixmap QReversiBoardView::chipPixmap(uint i, uint size) const
{
// Get the part of the 'allchips' pixmap that contains exactly that
// chip that we want to use.
QPixmap pix(CHIP_SIZE, CHIP_SIZE);
copyBlt(&pix, 0, 0, &allchips, (i%5) * CHIP_SIZE, (i/5) * CHIP_SIZE,
CHIP_SIZE, CHIP_SIZE);
// Resize (scale) the pixmap to the desired size.
QWMatrix wm3;
wm3.scale(float(size)/CHIP_SIZE, float(size)/CHIP_SIZE);
return pix.xForm(wm3);
}
uint QReversiBoardView::zoomedSize() const
{
return qRound(float(CHIP_SIZE) * Prefs::zoom() / 100);
}
void QReversiBoardView::drawPiece(uint row, uint col, Color color)
{
int i = (color == Nobody ? -1 : int(CHIP_OFFSET[color]));
drawOnePiece(row, col, i);
}
void QReversiBoardView::drawOnePiece(uint row, uint col, int i)
{
int px = col * zoomedSize() + 1;
int py = row * zoomedSize() + 1;
QPainter p(this);
// Draw either a background pixmap or a background color to the square.
int offset = m_marksShowing ? OFFSET() : 0;
if (bg.width())
p.drawTiledPixmap(px + offset, py + offset,
zoomedSize(), zoomedSize(), bg, px, py);
else
p.fillRect(px + offset, py + offset,
zoomedSize(), zoomedSize(), bgColor);
// Draw a black border around the square.
p.setPen(black);
p.drawRect(px + offset, py + offset, zoomedSize(), zoomedSize());
// If no piece on the square, i.e. only the background, then return here...
if ( i == -1 )
return;
// ...otherwise finally draw the piece on the square.
p.drawPixmap(px + offset, py + offset, chipPixmap(i, zoomedSize()));
}
// We got a repaint event. We make it easy for us and redraw the
// entire board.
//
void QReversiBoardView::paintEvent(QPaintEvent *)
{
updateBoard(true);
}
void QReversiBoardView::adjustSize()
{
int w = 8 * zoomedSize();
if (m_marksShowing)
w += 2 * OFFSET();
setFixedSize(w + 2, w + 2);
}
void QReversiBoardView::setPixmap(QPixmap &pm)
{
if ( pm.width() == 0 )
return;
bg = pm;
update();
setErasePixmap(pm);
}
void QReversiBoardView::setColor(const QColor &c)
{
bgColor = c;
bg = QPixmap();
update();
setEraseColor(c);
}
// Load all settings that have to do with the board view, such as
// piece colors, background, animation speed, an so on.
void QReversiBoardView::loadSettings()
{
// Colors of the pieces (red/blue or black/white)
if ( Prefs::grayscale() ) {
if (chiptype != Grayscale)
loadChips(Grayscale);
}
else {
if (chiptype != Colored)
loadChips(Colored);
}
// Animation speed.
if ( !Prefs::animation() )
setAnimationSpeed(0);
else
setAnimationSpeed(10 - Prefs::animationSpeed());
// Background
if ( Prefs::backgroundImageChoice() ) {
QPixmap pm( Prefs::backgroundImage() );
if (!pm.isNull())
setPixmap(pm);
} else {
setColor( Prefs::backgroundColor() );
}
}
#include "board.moc"