File: textui.cc

package info (click to toggle)
atom4 4.1-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 664 kB
  • ctags: 947
  • sloc: cpp: 4,451; makefile: 52; sh: 45; perl: 6
file content (385 lines) | stat: -rw-r--r-- 10,558 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
 * Atom-4 ncurses interface
 *
 * $Id: textui.cc,v 1.33 2003/04/09 18:47:36 hsteoh Exp hsteoh $
 */

#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>			// for STDIN_FILENO
#include "exception.h"
#include "game.h"
#include "textui.h"


#define NORMAL_COLOR		9

void ncurses_ui::keyhandler::read_ready(eventloop *src, int fd) {
  ui->handlekey();			// process event
  ui->refresh();			// update screen with any changes
}

void ncurses_ui::keyhandler::write_ready(eventloop *src, int fd) {}

void ncurses_ui::changehandler::notify_move(atom4 *src, int player,
                                            elist<boardchange> &changes) {
  // First change is always player's actual move
  boardchange move = *changes.headp();
  ui->message("Player %d moves (%d,%d)\n", player, move.x, move.y);
  notify_clear(src);
}

void ncurses_ui::changehandler::notify_clear(atom4 *src) {
  int winner = ui->game->winner();

  if (winner!=-1) {
    if (winner!=STALEMATE) {
      ui->message("Player %d WINS!!\n", winner);
    } else {
      ui->message("STALEMATE: no more moves left\n");
    }
  }
  ui->refresh();			// update with changes
}

void ncurses_ui::handlekey() {
  int pl = game->current_player();
  int ch = wgetch(bwin);

  if (ch==-1)
    throw exception("Ncurses error: aborting");

  // Global keybindings
  switch (ch) {
  case 0x0C:
    redrawwin(stdscr);			// force repaint of entire screen
    return;
  }

  // Process key differently depending on game state
  if (!game->round_over()) {
    switch (ch) {
    case KEY_UP:	if (last_y>0) last_y--;				break;
    case KEY_DOWN:	if (last_y<game->board_height()-1) last_y++;	break;
    case KEY_LEFT:	if (last_x>0) last_x--;				break;
    case KEY_RIGHT:	if (last_x<game->board_width()-1) last_x++;	break;
    case KEY_ENTER:
    case '\n':	case '\r':	case ' ':
      if (!game->is_local_turn()) {
        message("It's not your turn to move\n");
        break;
      }

      // Attempt the move
      if (!game->move(pl, last_x, last_y)) {
        message("You can't put a marble here");
      } else {
        if (!game->round_over()==-1) {
          message("Player %d moves (%d,%d)", pl, last_x, last_y);
        }
      }
      break;
    case 'q':
      if (ask_yn("Quit current game?"))
        *exitflag=1;			// quit (TBD: should resign player)
      break;
    default:
      message("Unknown key (%d)", ch);
    }
  } else {				// not in playing mode
    switch (ch) {
    case 'n':
      game->newround();			// start next round
      last_x=game->board_width()/2;	// reset cursor pos
      last_y=game->board_height()/2;
      message("");			// clean up stale messages
      break;
    case 'q':
      *exitflag=1;			// quit
      break;
    default:
      message("Press 'n' to proceed to the next round, 'q' to quit.");
    }
  }
}

int ncurses_ui::formatstr(char *fmt, ...) {
  va_list args;
  int rc;

  va_start(args,fmt);
  rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args);
  va_end(args);

  return rc;
}

int ncurses_ui::message(char *fmt, ...) {
  va_list args;
  int rc;

  va_start(args,fmt);
  rc=vsnprintf(strbuf, STRBUF_SIZE, fmt, args);
  mvwaddstr(msgwin, 0, 0, strbuf);
  wclrtoeol(msgwin);
  wrefresh(msgwin);
  va_end(args);

  return rc;
}

void ncurses_ui::draw_tile(WINDOW *w, celltype cell) {
  char *glyph;
  int color, bold=0;

  if (cell>='a' && cell<='h') {
    glyph="()";
    color = cellcolors[cell-'a'];
    bold = cellbold[cell-'a'];
  } else {
    // NOTE: this is an ugly hack to force ncurses to update every char
    // position with the white background; otherwise on some terminals they
    // use the default blank which may not have the right background.
    // Basically, we alternate between cell colors 0 and 1 (red); it doesn't
    // really matter because we're drawing blanks anyway. We take care to
    // use 0 for the first character so that the cursor doesn't show up with
    // funny colors on xterm.
    wcolor_set(w, cellcolors[0], NULL);
    waddch(w, ' ');
    color = cellcolors[1];
    glyph=" ";
  }

  wcolor_set(w, color, NULL);
  if (bold) wattron(w, A_BOLD);		// so that white cells will show
  waddstr(w, glyph);
  if (bold) wattroff(w, A_BOLD);	// normal for everything else
  wcolor_set(w, NORMAL_COLOR, NULL);	// just need the white background
}

void render_cell(int x, int y, celltype cell, void *context) {
  ncurses_ui *iface = (ncurses_ui*)context;

  wmove(iface->bwin, y, x*2 + (y%2) + 1);
  iface->draw_tile(iface->bwin, cell);
}

void ncurses_ui::render_board() {
  int i, wd;

  // Draw board
  game->get_board()->mapcell(render_cell, (void*)this);

  // Draw board borders
  wcolor_set(bwin, cellcolors[0], NULL); // just need the white background
  wd = game->board_width();
  for (i=0; i<game->board_height(); i++) {
    mvwaddch(bwin, i, 0, '|');

    if (i%2) {
      waddch(bwin, ' ');		// so we don't get color gaps in bckgnd
      mvwaddch(bwin, i, wd*2+2, '|');
    } else {
      mvwaddstr(bwin, i, wd*2+1, " |");
    }
  }
  wcolor_set(bwin, NORMAL_COLOR, NULL);	// just need the white background
}

void ncurses_ui::render_scorepanel(int player) {
  int plidx = player-1;
  int start_y;				// ycoor of this player's scoreboard

  start_y = IOWIN_PLPANEL_Y + plidx*IOWIN_PLPANEL_HEIGHT;

  if (use_color()) wcolor_set(iowin, colors[plidx], NULL);
  formatstr("** Player %d **", player);
  mvwaddstr(iowin, start_y+IOWIN_PANEL_TITLE_Y, 0, strbuf);
  if (use_color()) wcolor_set(iowin, NORMAL_COLOR, NULL);

  formatstr("Score: %d", game->score(player));
  mvwaddstr(iowin, start_y+IOWIN_PANEL_SCORE_Y, 0, strbuf);
}

void ncurses_ui::render_iowin() {
  int i;

  werase(iowin);
  wattron(iowin, A_REVERSE);
  mvwaddstr(iowin, IOWIN_TITLE_Y, 0, " Welcome to Atom-4 " VERSION_STRING " ");
  wclrtoeol(iowin);
  wattroff(iowin, A_REVERSE);

  formatstr("=== Round %d ===", game->current_round());
  mvwaddstr(iowin, IOWIN_ROUND_Y, 0, strbuf);

  for (i=1; i<=NUM_PLAYERS; i++) {
    render_scorepanel(i);
  }

  // Output a prompt for current player
  // Note: this should come last, so that the cursor is left at the right
  // place when wait_key() is called.
  if (!game->round_over()) {
    mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y +
              (game->current_player()-1)*IOWIN_PLPANEL_HEIGHT, 0,
              "-- Your turn: ");
    draw_tile(iowin, game->current_tile());
    waddstr(iowin, " --");
  } else {
    if (game->winner() != STALEMATE) {
      mvwaddstr(iowin, IOWIN_PLPANEL_Y + IOWIN_PANEL_PROMPT_Y +
                (game->winner()-1)*IOWIN_PLPANEL_HEIGHT, 0,
                "!!! WINNER !!!");
    }
  }
}

void ncurses_ui::refresh() {
  render_board();
  render_iowin();
  update_panels();
  doupdate();

  if (game->is_local_turn()) {		// update cursor pos
    wmove(bwin, last_y, last_x*2+(last_y%2)+1);
    wrefresh(bwin);
  }
}

int ncurses_ui::wait_key() {
  refresh();				// now is a good time to refresh
  return wgetch(iowin);
}

int ncurses_ui::ask_yn(char *question) {
  int ch;

  message("%s (y/n) ", question);
  for (;;) {
    ch=wgetch(msgwin);
    if (ch=='n' || ch=='N') {
      waddstr(msgwin, "no");
      wrefresh(msgwin);
      return 0;
    }
    if (ch=='y' || ch=='Y') {
      waddstr(msgwin, "yes");
      wrefresh(msgwin);
      return 1;
    }
  }

  // Never reached
  return 0;
}

int ncurses_ui::init_color() {
  if (start_color()==ERR) return 0;
  if (has_colors()==FALSE) return 0;
  if (use_default_colors()==ERR) return 0;

  // Insufficient color pairs
  if (COLOR_PAIRS <= 9) return 0;

  if (init_pair(NORMAL_COLOR, -1, -1)==ERR) return 0;

  if (init_pair(1, COLOR_BLACK,   COLOR_WHITE)==ERR) return 0;
  if (init_pair(2, COLOR_RED,     COLOR_WHITE)==ERR) return 0;
  if (init_pair(3, COLOR_GREEN,   COLOR_WHITE)==ERR) return 0;
  if (init_pair(4, COLOR_BLUE,    COLOR_WHITE)==ERR) return 0;
  if (init_pair(5, COLOR_YELLOW,  COLOR_WHITE)==ERR) return 0;
  if (init_pair(6, COLOR_CYAN,    COLOR_WHITE)==ERR) return 0;
  if (init_pair(7, COLOR_MAGENTA, COLOR_WHITE)==ERR) return 0;
  if (init_pair(8, COLOR_WHITE,   COLOR_WHITE)==ERR) return 0;

  // Bold the secondary colors so that they are easily distinguished from
  // the primary colors (blue and cyan are esp. easy to confuse)
  cellcolors[0] = 1; cellbold[0] = 0;
  cellcolors[1] = 2; cellbold[1] = 0;
  cellcolors[2] = 3; cellbold[2] = 0;
  cellcolors[3] = 5; cellbold[3] = 1;
  cellcolors[4] = 4; cellbold[4] = 0;
  cellcolors[5] = 7; cellbold[5] = 1;
  cellcolors[6] = 6; cellbold[6] = 1;
  cellcolors[7] = 8; cellbold[7] = 1;

  // Player colors: maybe we should just revert to normal color?
  colors[0] = NORMAL_COLOR;
  colors[1] = NORMAL_COLOR;

  return 1;
}

ncurses_ui::ncurses_ui(atom4 *gm, eventloop *eloop, int *indicator, int opts) :
	interface(gm), options(opts), loop(eloop), exitflag(indicator),
	khandler(this), notifier(this) {
  int bwd, bht;
  int i;

  /* Init ncurses */
  initscr();

  /* Color support -- now it's mandatory since we can't easily represent
   * 8 colors in monochrome! */
  if (!init_color()) {
      throw exception("Cannot initialize color subsystem\n");
//      options &= ~ENABLE_COLOR;		// color initialization failed
  }

  /* General options */
  cbreak();
  noecho();
  nonl();

  /* Setup ncurses window for board */
  bwd = game->board_width()*2+3;
  bht = game->board_height();
  bwin = newwin(bht, bwd, BOARDWIN_Y, BOARDWIN_X);
  bpanel = new_panel(bwin);
  scrollok(bwin, FALSE);
  keypad(bwin, TRUE);

  /* Put selection cursor at roughly the center of the board */
  last_x = game->board_width()/2;
  last_y = game->board_height()/2;

  /* Setup ncurses window for displaying player stats */
  iowin = newwin(LINES-IOWIN_Y, COLS-bwd-1, IOWIN_Y, BOARDWIN_X+bwd+1);
  iopanel = new_panel(iowin);
  scrollok(bwin, FALSE);
  keypad(iowin, TRUE);			// else ESC's get lost when we switch
					// between panes

  /* Setup general message panel */
  msgwin = newwin(1, COLS, BOARDWIN_Y + bht + 1, 0);
  msgpanel = new_panel(msgwin);
  scrollok(msgwin, FALSE);
  keypad(msgwin, TRUE);			// else ESC's get lost when we switch
					// between panes

  /* Initial screen refresh */
  refresh();

  /* Setup async callbacks */
  loop->register_handler(eventloop::READER, STDIN_FILENO, &khandler);
  game->add_notifier(&notifier);
}

ncurses_ui::~ncurses_ui() {
  /* Get rid of message panel */
  del_panel(msgpanel);
  delwin(msgwin);

  /* Get rid of scoreboard panel */
  del_panel(iopanel);
  delwin(iowin);

  /* Get rid of board panel */
  del_panel(bpanel);
  delwin(bwin);

  /* Exit ncurses mode */
  endwin();
}