/***************************************************************************** * Copyright (C) 2005-2008 Eliot * Authors: Olivier Teuliere * * This program 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 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *****************************************************************************/ #include "config.h" #if ENABLE_NLS # include # define _(String) gettext(String) #else # define _(String) String #endif #ifdef WIN32 # include #endif #include #include // For strlen #include #include #include "ncurses.h" #include "dic.h" #include "game_factory.h" #include "training.h" #include "duplicate.h" #include "freegame.h" #include "player.h" #include "history.h" #include "turn.h" #include "encoding.h" using namespace std; Box::Box(WINDOW *win, int y, int x, int h, int w, unsigned int iHeadingLines) : m_win(win), m_x(x), m_y(y), m_w(w), m_h(h), m_topLine(y + 1 + iHeadingLines), m_nbLines(h - 2 - iHeadingLines), m_dataStart(0), m_dataSize(0) { } void Box::draw(const string& iTitle) const { if (m_w > 3 && m_h > 2) { // Add one space before and after the title for readability string title; if (!iTitle.empty()) title = " " + iTitle + " "; unsigned int l = title.size(); // Truncate the title if needed if ((int)l > m_w - 2) l = m_w - 2; mvwaddch(m_win, m_y, m_x, ACS_ULCORNER); mvwhline(m_win, m_y, m_x + 1, ACS_HLINE, (m_w - l - 2)/2); mvwprintw(m_win,m_y, m_x + 1 + (m_w - l - 2)/2, "%s", title.c_str()); mvwhline(m_win, m_y, m_x + (m_w - l)/2 + l, ACS_HLINE, m_w - 1 - ((m_w - l)/2 + l)); mvwaddch(m_win, m_y, m_x + m_w - 1, ACS_URCORNER); mvwvline(m_win, m_y + 1, m_x, ACS_VLINE, m_h - 2); mvwvline(m_win, m_y + 1, m_x + m_w - 1, ACS_VLINE, m_h - 2); mvwaddch(m_win, m_y + m_h - 1, m_x, ACS_LLCORNER); mvwhline(m_win, m_y + m_h - 1, m_x + 1, ACS_HLINE, m_w - 2); mvwaddch(m_win, m_y + m_h - 1, m_x + m_w - 1, ACS_LRCORNER); } } void Box::printDataLine(int n, int x, const char *fmt, ...) const { if (n < getFirstLine() || n >= getLastLine() || m_w <= x - m_x + 1) return; va_list vl_args; char *buf = NULL; va_start(vl_args, fmt); vasprintf(&buf, fmt, vl_args); va_end(vl_args); if (buf == NULL) { return; } mvwprintw(m_win, m_topLine + n - m_dataStart, x, "%s", truncString(buf, m_w - 1 - x + m_x).c_str()); free(buf); } bool Box::scrollOneLineUp() { if (m_dataSize <= m_nbLines || m_dataStart == 0) return false; m_dataStart--; return true; } bool Box::scrollOneLineDown() { if (m_dataSize <= m_nbLines || m_dataStart >= m_dataSize - 1) return false; m_dataStart++; return true; } bool Box::scrollOnePageUp() { if (m_dataSize <= m_nbLines) return false; m_dataStart -= m_nbLines; if (m_dataStart < 0) m_dataStart = 0; return true; } bool Box::scrollOnePageDown() { if (m_dataSize <= m_nbLines) return false; m_dataStart += m_nbLines; if (m_dataStart > m_dataSize - 1) m_dataStart = m_dataSize - 1; return true; } bool Box::scrollBeginning() { if (m_dataSize <= m_nbLines || m_dataStart == 0) return false; m_dataStart = 0; return true; } bool Box::scrollEnd() { if (m_dataSize <= m_nbLines || m_dataStart == m_dataSize - 1) return false; m_dataStart = m_dataSize - 1; return true; } void Box::clearRect(WINDOW *win, int y, int x, int h, int w) { for (int i = 0; i < h; i++) { mvwhline(win, y + i, x, ' ', w); } } CursesIntf::CursesIntf(WINDOW *win, Game& iGame) : m_win(win), m_game(&iGame), m_state(DEFAULT), m_dying(false), m_box(win, 0, 0, 0, 0), m_showDots(false) { } CursesIntf::~CursesIntf() { GameFactory::Instance()->releaseGame(*m_game); GameFactory::Destroy(); } void CursesIntf::drawStatus(WINDOW *win, const string& iMessage, bool error) { int cols; int lines; getmaxyx(win, lines, cols); int x = 0; int y = lines - 1; if (error) wattron(win, COLOR_PAIR(COLOR_YELLOW)); mvwprintw(win, y, x, truncOrPad(iMessage, cols).c_str()); if (error) wattron(win, COLOR_PAIR(COLOR_WHITE)); } void CursesIntf::drawBoard(WINDOW *win, int y, int x) const { // Box around the board Box box(win, y + 1, x + 3, 17, 47); box.draw(); // Print the coordinates for (int i = 0; i < 15; i++) { mvwaddch(win, y + i + 2, x + 1, 'A' + i); mvwaddch(win, y + i + 2, x + 51, 'A' + i); mvwprintw(win, y, x + 3 * i + 5, "%d", i + 1); mvwprintw(win, y + 18, x + 3 * i + 5, "%d", i + 1); } // The board itself for (int row = 1; row < 16; row++) { for (int col = 1; col < 16; col++) { // Handle colors int wm = m_game->getBoard().GetWordMultiplier(row, col); int lm = m_game->getBoard().GetLetterMultiplier(row, col); if (wm == 3) wattron(win, COLOR_PAIR(COLOR_RED)); else if (wm == 2) wattron(win, COLOR_PAIR(COLOR_MAGENTA)); else if (lm == 3) wattron(win, COLOR_PAIR(COLOR_BLUE)); else if (lm == 2) wattron(win, COLOR_PAIR(COLOR_CYAN)); else wattron(win, COLOR_PAIR(COLOR_WHITE)); // Empty square mvwprintw(win, y + row + 1, x + 3 * col + 1, " "); // Now add the letter wchar_t c = m_game->getBoard().getChar(row, col); if (c) { cchar_t cc; if (iswlower(c)) { setcchar(&cc, &c, A_BOLD, COLOR_GREEN, NULL); mvwadd_wch(win, y + row + 1, x + 3 * col + 2, &cc); } else { setcchar(&cc, &c, 0, 0, NULL); mvwadd_wch(win, y + row + 1, x + 3 * col + 2, &cc); } } else { // Empty square... should we display a dot? if (m_showDots) mvwaddch(win, y + row + 1, x + 3 * col + 2, '.'); } } } wattron(win, COLOR_PAIR(COLOR_WHITE)); } void CursesIntf::drawScoresRacks(WINDOW *win, int y, int x) const { // Compute the longest player name size_t longest = 0; for (unsigned int i = 0; i < m_game->getNPlayers(); i++) { longest = std::max(longest, m_game->getPlayer(i).getName().size()); } Box box(win, y, x, m_game->getNPlayers() + 2, 25); box.draw(_("Scores")); // Magic formula to truncate too long names unsigned int maxForScores = std::min(longest, box.getWidth() - strlen(_("%s: %d")) - 1); for (unsigned int i = 0; i < m_game->getNPlayers(); i++) { if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer()) attron(A_BOLD); mvwprintw(win, y + i + 1, x + 2, _("%s: %d"), truncOrPad(convertToMb(m_game->getPlayer(i).getName()), maxForScores).c_str(), m_game->getPlayer(i).getPoints()); if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer()) attroff(A_BOLD); } // Distance between the 2 boxes unsigned int yOff = m_game->getNPlayers() + 3; Box box2(win, y + yOff, x, m_game->getNPlayers() + 2, 25); box2.draw(_("Racks")); // Magic formula to truncate too long names unsigned int maxForRacks = std::min(longest, box.getWidth() - strlen(_("%s: %ls")) - 4); for (unsigned int i = 0; i < m_game->getNPlayers(); i++) { if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer()) attron(A_BOLD); wstring rack = m_game->getPlayer(i).getCurrentRack().toString(PlayedRack::RACK_SIMPLE); mvwprintw(win, y + yOff + i + 1, x + 2, _("%s: %ls"), truncOrPad(convertToMb(m_game->getPlayer(i).getName()), maxForRacks).c_str(), rack.c_str()); if (m_game->getMode() != Game::kTRAINING && i == m_game->currPlayer()) attroff(A_BOLD); // Force to refresh the whole rack whline(win, ' ', 7 - rack.size()); } // Display a message when the search is complete if (m_game->getMode() == Game::kTRAINING && static_cast(m_game)->getResults().size()) { mvwprintw(win, y + 2*yOff - 1, x + 2, _("Search complete")); } else mvwhline(win, y + 2*yOff - 1, x + 2, ' ', strlen(_("Search complete"))); } void CursesIntf::drawResults(Box &ioBox) const { if (m_game->getMode() != Game::kTRAINING) return; Training *tr_game = static_cast(m_game); ioBox.draw(_("Search results")); ioBox.setDataSize(tr_game->getResults().size()); unsigned int i; const Results& res = tr_game->getResults(); int x = ioBox.getLeft(); for (i = (unsigned int)ioBox.getFirstLine(); i < res.size() && i < (unsigned int)ioBox.getLastLine(); i++) { const Round &r = res.get(i); wstring coord = r.getCoord().toString(); ioBox.printDataLine(i, x, "%3d %s %3s", r.getPoints(), padAndConvert(r.getWord(), ioBox.getWidth() - 9, false).c_str(), convertToMb(coord).c_str()); } // Complete the list with empty lines, to avoid trails for (; i < (unsigned int)ioBox.getLastLine(); i++) { ioBox.printDataLine(i, x + 1, string(ioBox.getWidth(), ' ').c_str()); } } void CursesIntf::drawHistory(Box &ioBox) const { // To allow pseudo-scrolling, without leaving trails ioBox.clearData(); ioBox.draw(_("History of the game")); ioBox.setDataSize((int)m_game->getHistory().getSize()); int x = ioBox.getLeft(); int y = ioBox.getTop(); // Heading string heading = truncString(_(" N | RACK | SOLUTION | REF | PTS | P | BONUS"), ioBox.getWidth() - 1); mvwprintw(m_win, y, x + 1, "%s", heading.c_str()); mvwhline(m_win, y + 1, x + 1, ACS_HLINE, heading.size()); int i; for (i = ioBox.getFirstLine(); i < (int)m_game->getHistory().getSize() && i < ioBox.getLastLine(); i++) { const Turn& t = m_game->getHistory().getTurn(i); const Move& m = t.getMove(); if (m.getType() == Move::VALID_ROUND) { // The move corresponds to a played round: display it const Round &r = m.getRound(); wstring coord = r.getCoord().toString(); ioBox.printDataLine(i, x, " %2d %s %s %s %3d %1d %c", i + 1, padAndConvert(t.getPlayedRack().toString(), 8).c_str(), padAndConvert(r.getWord(), 15, false).c_str(), padAndConvert(coord, 3).c_str(), r.getPoints(), t.getPlayer(), r.getBonus() ? '*' : ' '); } else if (m.getType() == Move::INVALID_WORD) { // The move corresponds to an invalid word: display it wstring invWord = L"<" + m.getBadWord() + L">"; ioBox.printDataLine(i, x, " %2d %s %s %s %3d %1d", i + 1, padAndConvert(t.getPlayedRack().toString(), 8).c_str(), padAndConvert(invWord, 15, false).c_str(), padAndConvert(m.getBadCoord(), 3).c_str(), m.getScore(), t.getPlayer()); } else { // The move corresponds to a passed turn or changed letters wstring action; if (m.getType() == Move::PASS) action = convertToWc(_("(PASS)")); else if (m.getType() == Move::CHANGE_LETTERS) action = L"(-" + m.getChangedLetters() + L")"; ioBox.printDataLine(i, x, " %2d %s %s %s %3d %1d", i + 1, padAndConvert(t.getPlayedRack().toString(), 8).c_str(), padAndConvert(action, 15, false).c_str(), " - ", m.getScore(), t.getPlayer()); } } int nbLines = min(i + 2 - ioBox.getFirstLine(), ioBox.getLastLine() - ioBox.getFirstLine() + 2); mvwvline(m_win, y, x + 4, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 15, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 33, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 39, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 45, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 49, ACS_VLINE, nbLines); } void CursesIntf::drawHelp(Box &ioBox) const { // To allow pseudo-scrolling, without leaving trails ioBox.clearData(); ioBox.draw(_("Help")); int x = ioBox.getLeft() + 1; int n = 0; ioBox.printDataLine(n++, x, _("[Global]")); ioBox.printDataLine(n++, x, _(" h, H, ? Show/hide help box")); ioBox.printDataLine(n++, x, _(" y, Y Show/hide history of the game")); ioBox.printDataLine(n++, x, _(" b, B Show/hide contents of the bag (including letters of the racks)")); ioBox.printDataLine(n++, x, _(" e, E Show/hide dots on empty squares of the board")); ioBox.printDataLine(n++, x, _(" d, D Check the existence of a word in the dictionary")); ioBox.printDataLine(n++, x, _(" j, J Play a word")); ioBox.printDataLine(n++, x, _(" s, S Save the game")); ioBox.printDataLine(n++, x, _(" l, L Load a game")); ioBox.printDataLine(n++, x, _(" q, Q Quit")); ioBox.printDataLine(n++, x, ""); ioBox.printDataLine(n++, x, _("[Training mode]")); ioBox.printDataLine(n++, x, _(" * Take a random rack")); ioBox.printDataLine(n++, x, _(" + Complete the current rack randomly")); ioBox.printDataLine(n++, x, _(" t, T Set the rack manually")); ioBox.printDataLine(n++, x, _(" c, C Compute all the possible words")); ioBox.printDataLine(n++, x, _(" r, R Show/hide search results")); ioBox.printDataLine(n++, x, ""); ioBox.printDataLine(n++, x, _("[Duplicate mode]")); ioBox.printDataLine(n++, x, _(" n, N Switch to the next human player")); ioBox.printDataLine(n++, x, ""); ioBox.printDataLine(n++, x, _("[Free game mode]")); ioBox.printDataLine(n++, x, _(" p, P Pass your turn (with or without changing letters)")); ioBox.printDataLine(n++, x, ""); ioBox.printDataLine(n++, x, _("[Miscellaneous]")); ioBox.printDataLine(n++, x, _(" , Navigate in a box line by line")); ioBox.printDataLine(n++, x, _(" , Navigate in a box page by page")); ioBox.printDataLine(n++, x, _(" Ctrl-l Refresh the screen")); ioBox.setDataSize(n); } void CursesIntf::drawBag(Box &ioBox) const { // To allow pseudo-scrolling, without leaving trails ioBox.clearData(); ioBox.draw(_("Bag")); vector allTiles = m_game->getDic().getAllTiles(); ioBox.setDataSize(allTiles.size()); int x = ioBox.getLeft(); int y = ioBox.getTop(); // Heading string heading = truncString(_(" LETTER | POINTS | FREQUENCY | REMAINING"), ioBox.getWidth() - 1); mvwprintw(m_win, y, x + 1, "%s", heading.c_str()); mvwhline(m_win, y + 1, x + 1, ACS_HLINE, heading.size()); int i; for (i = ioBox.getFirstLine(); i < (int)allTiles.size() && i < ioBox.getLastLine(); i++) { ioBox.printDataLine(i, ioBox.getLeft() + 1, " %s %2d %2d %s", padAndConvert(wstring(1, allTiles[i].toChar()), 2).c_str(), allTiles[i].getPoints(), allTiles[i].maxNumber(), convertToMb(wstring(m_game->getBag().in(allTiles[i]), allTiles[i].toChar())).c_str()); } int nbLines = min(i + 2 - ioBox.getFirstLine(), ioBox.getLastLine() - ioBox.getFirstLine() + 2); mvwvline(m_win, y, x + 9, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 18, ACS_VLINE, nbLines); mvwvline(m_win, y, x + 30, ACS_VLINE, nbLines); } void CursesIntf::setState(State iState) { // Clear the previous box m_box.clear(); // Get the size of the screen (better than using COLS and LINES directly, // according to the manual) int lines; int cols; getmaxyx(m_win, lines, cols); m_state = iState; if (m_state == DEFAULT) m_box = Box(m_win, 0, 0, 0, 0); else if (m_state == RESULTS) m_box = Box(m_win, 3, 54, 17, 25); else if (m_state == HISTORY) m_box = Box(m_win, 1, 0, lines - 1, cols, 2); else if (m_state == HELP) m_box = Box(m_win, 1, 0, lines - 1, cols); else if (m_state == BAG) m_box = Box(m_win, 1, 0, lines - 1, cols, 2); } void CursesIntf::playWord(WINDOW *win, int y, int x) { Box box(win, y, x, 4, 32); box.draw(_("Play a word")); mvwprintw(win, y + 1, x + 2, _("Played word:")); mvwprintw(win, y + 2, x + 2, _("Coordinates:")); wrefresh(win); // TRANSLATORS: Align the : when translating "Played word:" and // "Coordinates:". For example: // Pl. word : // Coordinates: int l1 = strlen(_("Played word:")); int l2 = strlen(_("Coordinates:")); int xOff; if (l1 > l2) xOff = l1 + 3; else xOff = l2 + 3; wstring word, coord; if (readString(win, y + 1, x + xOff, 15, word) && readString(win, y + 2, x + xOff, 3, coord)) { int res = m_game->play(coord, word); if (res) { drawStatus(win, _("Incorrect or misplaced word")); } } box.clear(); } void CursesIntf::checkWord(WINDOW *win, int y, int x) { Box box(win, y, x, 4, 32); box.draw(_("Dictionary")); mvwprintw(win, y + 1, x + 2, _("Enter the word to check:")); wrefresh(win); wstring word; if (readString(win, y + 2, x + 2, 15, word)) { bool res = m_game->getDic().searchWord(word); char s[100]; if (res) snprintf(s, 100, _("The word '%ls' exists"), word.c_str()); else snprintf(s, 100, _("The word '%ls' does not exist"), word.c_str()); drawStatus(win, s, false); } box.clear(); } void CursesIntf::saveGame(WINDOW *win, int y, int x) { Box box(win, y, x, 4, 32); box.draw(_("Save the game")); mvwprintw(win, y + 1, x + 2, _("Enter the file name:")); wrefresh(win); wstring filename; if (readString(win, y + 2, x + 2, 28, filename, kFILENAME)) { ofstream fout(convertToMb(filename).c_str()); char s[100]; if (fout.rdstate() == ios::failbit) { snprintf(s, 100, _("Cannot open file %ls for writing"), filename.c_str()); drawStatus(win, s); } else { m_game->save(fout, Game::FILE_FORMAT_ADVANCED); fout.close(); snprintf(s, 100, _("Game saved in '%ls'"), filename.c_str()); drawStatus(win, s, false); } } box.clear(); } void CursesIntf::loadGame(WINDOW *win, int y, int x) { Box box(win, y, x, 4, 32); box.draw(_("Load a game")); mvwprintw(win, y + 1, x + 2, _("Enter the file name:")); wrefresh(win); wstring filename; if (readString(win, y + 2, x + 2, 28, filename, kFILENAME)) { char s[100]; FILE *fin; if ((fin = fopen(convertToMb(filename).c_str(), "r")) == NULL) { snprintf(s, 100, _("Cannot open file '%ls' for reading"), filename.c_str()); } else { Game *loaded = Game::load(fin, m_game->getDic()); if (loaded == NULL) { snprintf(s, 100, _("Invalid saved game")); drawStatus(win, s); } else { snprintf(s, 100, _("Game loaded")); GameFactory::Instance()->releaseGame(*m_game); m_game = loaded; drawStatus(win, s, false); } fclose(fin); } } box.clear(); } void CursesIntf::passTurn(WINDOW *win, int y, int x, FreeGame &iGame) { Box box(win, y, x, 4, 32); box.draw(_("Pass your turn")); mvwprintw(win, y + 1, x + 2, _("Enter the letters to change:")); wrefresh(win); wstring letters; if (readString(win, y + 2, x + 2, 7, letters)) { int res = iGame.pass(letters); if (res) { drawStatus(win, _("Cannot pass the turn")); } } box.clear(); } void CursesIntf::setRack(WINDOW *win, int y, int x, Training &iGame) { Box box(win, y, x, 4, 32); box.draw(_("Set rack")); mvwprintw(win, y + 1, x + 2, _("Enter the new letters:")); wrefresh(win); wstring letters; if (readString(win, y + 2, x + 2, 7, letters, kJOKER)) { int res = iGame.setRackManual(false, letters); if (res) { drawStatus(win, _("Cannot take these letters from the bag")); } } m_state = DEFAULT; box.clear(); } bool CursesIntf::readString(WINDOW *win, int y, int x, int n, wstring &oString, unsigned int flag) { // Save the initial position int x0 = x; wint_t c; wmove(win, y, x); curs_set(1); int res; // Position in the string before which to insert the next character // (the character will be added at the end if pos == oString.size()) unsigned int pos = 0; while ((res = get_wch(&c)) != ERR) { if (c == 0x1b ) // Esc { curs_set(0); return false; } else if ((c == KEY_ENTER && res == KEY_CODE_YES) || c == 0xD) { curs_set(0); return true; } else if (c == 0x0c) // Ctrl-L { redraw(win); wmove(win, y, x); } else if (c == 0x0b) // Ctrl-K { // Remove everything after the cursor position int len = oString.size() - pos; oString = oString.erase(pos); mvwprintw(win, y, x, string(len, ' ').c_str()); wmove(win, y, x); } else if (c == 0x15) // Ctrl-U { // Remove everything before the cursor position oString.erase(0, pos); int len = pos; x = x0; pos = 0; mvwprintw(win, y, x0, "%s", convertToMb(oString + wstring(len, L' ')).c_str()); wmove(win, y, x); } else if (res == KEY_CODE_YES) { if (c == KEY_BACKSPACE && pos != 0) { x--; pos--; oString.erase(pos, 1); mvwprintw(win, y, x0, "%s", convertToMb(oString + L" ").c_str()); wmove(win, y, x); } else if (c == KEY_DC) { oString.erase(pos, 1); mvwprintw(win, y, x0, "%s", convertToMb(oString + L" ").c_str()); wmove(win, y, x); } else if (c == KEY_LEFT && pos != 0) { x--; pos--; wmove(win, y, x); } else if (c == KEY_RIGHT && pos != oString.size()) { x++; pos++; wmove(win, y, x); } else if (c == KEY_HOME) { x = x0; pos = 0; wmove(win, y, x); } else if (c == KEY_END) { x = x0 + oString.size(); pos = oString.size(); wmove(win, y, x); } else beep(); } else if (res == OK && iswalnum(c) && oString.size() < (unsigned int)n) { x++; oString.insert(pos++, 1, c); mvwprintw(win, y, x0, "%s", convertToMb(oString).c_str()); wmove(win, y, x); } else if (flag & kJOKER && c == L'?') { x++; oString.insert(pos++, 1, c); mvwprintw(win, y, x0, "%s", convertToMb(oString).c_str()); wmove(win, y, x); } else if (flag & kFILENAME) { if (c == L'/' || c == L'.' || c == L'-' || c == L'_' || c == L' ') { x++; oString += c; mvwprintw(win, y, x0, "%s", convertToMb(oString).c_str()); wmove(win, y, x); } else beep(); } else beep(); } curs_set(0); return false; } int CursesIntf::handleKeyForGame(int iKey, Training &iGame) { switch (iKey) { case '*': if (m_state != DEFAULT) { setState(DEFAULT); redraw(m_win); } iGame.setRackRandom(false, Game::RACK_ALL); return 1; case '+': if (m_state != DEFAULT) { setState(DEFAULT); redraw(m_win); } iGame.setRackRandom(false, Game::RACK_NEW); return 1; case 't': case 'T': if (m_state != DEFAULT) { setState(DEFAULT); redraw(m_win); } setRack(m_win, 22, 10, iGame); return 1; case 'c': case 'C': iGame.search(); return 1; default: return 2; } } int CursesIntf::handleKeyForGame(int iKey, Duplicate &iGame) { switch (iKey) { case 'n': case 'N': iGame.nextHumanPlayer(); return 1; default: return 2; } } int CursesIntf::handleKeyForGame(int iKey, FreeGame &iGame) { switch (iKey) { case 'p': case 'P': passTurn(m_win, 22, 10, iGame); return 1; default: return 2; } } int CursesIntf::handleKey(int iKey) { // Remove any error message in the status line if (m_state == DEFAULT || m_state == RESULTS) drawStatus(m_win, "", false); // Handle game-specific keys int res; if (m_game->getMode() == Game::kTRAINING) { res = handleKeyForGame(iKey, (Training&)*m_game); } else if (m_game->getMode() == Game::kDUPLICATE) { res = handleKeyForGame(iKey, (Duplicate&)*m_game); } else { res = handleKeyForGame(iKey, (FreeGame&)*m_game); } if (res != 2) return res; // Handle scrolling keys if (m_state != DEFAULT) { switch (iKey) { case KEY_HOME: return m_box.scrollBeginning() ? 1 : 0; case KEY_END: return m_box.scrollEnd() ? 1 : 0; case KEY_UP: return m_box.scrollOneLineUp() ? 1 : 0; case KEY_DOWN: return m_box.scrollOneLineDown() ? 1 : 0; case KEY_PPAGE: return m_box.scrollOnePageUp() ? 1 : 0; case KEY_NPAGE: return m_box.scrollOnePageDown() ? 1 : 0; } } // Handle other global keys switch (iKey) { // Toggle help case 'h': case 'H': case '?': if (m_state == HELP) setState(DEFAULT); else setState(HELP); clear(); return 1; // Toggle history case 'y': case 'Y': if (m_state == HISTORY) setState(DEFAULT); else setState(HISTORY); clear(); return 1; // Toggle results (training mode only) case 'r': case 'R': if (m_game->getMode() != Game::kTRAINING) { beep(); return 0; } if (m_state == RESULTS) setState(DEFAULT); else setState(RESULTS); Box::clearRect(m_win, 3, 54, 30, 25); return 1; // Toggle bag case 'b': case 'B': if (m_state == BAG) setState(DEFAULT); else setState(BAG); clear(); return 1; // Toggle dots display case 'e': case 'E': m_showDots = !m_showDots; return 1; // Ctrl-L should clear and redraw the screen case 0x0c: clear(); // Force the re-definition of the current box setState(m_state); return 1; // Check a word in the dictionary case 'd': case 'D': if (m_state != DEFAULT && m_state != RESULTS) { setState(DEFAULT); redraw(m_win); } checkWord(m_win, 22, 10); return 0; // Play a word case 'j': case 'J': if (m_state != DEFAULT && m_state != RESULTS) { setState(DEFAULT); redraw(m_win); } playWord(m_win, 22, 10); return 1; case 'l': case 'L': if (m_state != DEFAULT) { setState(DEFAULT); redraw(m_win); } loadGame(m_win, 22, 10); return 1; case 's': case 'S': if (m_state != DEFAULT) { setState(DEFAULT); redraw(m_win); } saveGame(m_win, 22, 10); return 0; // Quit case 'q': case 'Q': m_dying = true; return 0; default: beep(); return 0; } } void CursesIntf::redraw(WINDOW *win) { if (m_state == DEFAULT) { drawScoresRacks(win, 3, 54); drawBoard(win, 2, 0); } else if (m_state == RESULTS) { drawResults(m_box); drawBoard(win, 2, 0); } else if (m_state == HELP) { drawHelp(m_box); } else if (m_state == HISTORY) { drawHistory(m_box); } else if (m_state == BAG) { drawBag(m_box); } // Title attron(A_REVERSE); string mode; if (m_game->getMode() == Game::kTRAINING) mode = _("Training mode"); else if (m_game->getMode() == Game::kFREEGAME) mode = _("Free game mode"); else if (m_game->getMode() == Game::kDUPLICATE) mode = _("Duplicate mode"); string variant = ""; if (m_game->getVariant() == Game::kJOKER) variant = string(" - ") + _("Joker game"); string title = "Eliot (" + mode + variant + ") " + _("[h for help]"); int lines; int cols; getmaxyx(m_win, lines, cols); mvwprintw(win, 0, 0, truncOrPad(title, cols).c_str()); attroff(A_REVERSE); wrefresh(win); } int main(int argc, char ** argv) { #if HAVE_SETLOCALE // Set locale via LC_ALL setlocale(LC_ALL, ""); #endif #if ENABLE_NLS // Set the message domain #ifdef WIN32 // Get the absolute path, as returned by GetFullPathName() char localeDir[MAX_PATH]; GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); char *pos = strrchr(localeDir, L'\\'); if (pos) *pos = '\0'; #else static const char *localeDir = LOCALEDIR; #endif bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); #endif srand(time(NULL)); Game *game = GameFactory::Instance()->createFromCmdLine(argc, argv); if (game == NULL) { GameFactory::Destroy(); return 1; } game->start(); // Initialize the ncurses library WINDOW *wBoard = initscr(); keypad(wBoard, true); // Take input chars one at a time cbreak(); // Do not do NL -> NL/CR nonl(); // Hide the cursor curs_set(0); if (has_colors()) { start_color(); // Simple color assignment init_pair(COLOR_BLACK, COLOR_BLACK, COLOR_BLACK); init_pair(COLOR_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(COLOR_WHITE, COLOR_WHITE, COLOR_BLACK); init_pair(COLOR_YELLOW, COLOR_YELLOW, COLOR_RED); init_pair(COLOR_BLUE, COLOR_BLACK, COLOR_BLUE); init_pair(COLOR_CYAN, COLOR_BLACK, COLOR_CYAN); init_pair(COLOR_MAGENTA, COLOR_BLACK, COLOR_MAGENTA); init_pair(COLOR_RED, COLOR_BLACK, COLOR_RED); } // Do not echo noecho(); // mainIntf will take care of destroying game for us CursesIntf mainIntf(wBoard, *game); mainIntf.redraw(wBoard); while (!mainIntf.isDying()) { int c = getch(); if (mainIntf.handleKey(c) == 1) { mainIntf.redraw(wBoard); } } delwin(wBoard); // Exit the ncurses library endwin(); GameFactory::Destroy(); return 0; }