1
0
Fork 0
forked from len0rd/rockbox

FS #6509 - "Plugin for playing reversi game" by Alexander Levin + changes by me to make it compile due to the menu api change and compile for e200.

git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13745 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
Antoine Cellerier 2007-06-30 20:04:42 +00:00
parent 9789acc3e0
commit b4b34f0c07
9 changed files with 1449 additions and 0 deletions

View file

@ -12,6 +12,7 @@ rockboy
#ifdef HAVE_LCD_BITMAP #ifdef HAVE_LCD_BITMAP
chessbox chessbox
sudoku sudoku
reversi
#endif #endif
/* For all 2bpp and colour targets */ /* For all 2bpp and colour targets */

View file

@ -0,0 +1,111 @@
# __________ __ ___.
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
# \/ \/ \/ \/ \/
# $$Id: $$
#
INCLUDES = -I$(APPSDIR) -I.. -I. $(TARGET_INC) -I$(FIRMDIR)/include -I$(FIRMDIR)/export \
-I$(FIRMDIR)/common -I$(FIRMDIR)/drivers -I$(OUTDIR) -I$(BUILDDIR) \
-I$(BUILDDIR)/pluginbitmaps
CFLAGS = $(INCLUDES) $(GCCOPTS) $(TARGET) $(EXTRA_DEFINES) \
-DTARGET_ID=$(TARGET_ID) -DMEM=${MEMORYSIZE} -DPLUGIN
ifdef APPEXTRA
INCLUDES += $(patsubst %,-I$(APPSDIR)/%,$(subst :, ,$(APPEXTRA)))
endif
LINKFILE := $(OBJDIR)/link.lds
DEPFILE = $(OBJDIR)/dep-reversi
# This sets up 'SRC' based on the files mentioned in SOURCES
include $(TOOLSDIR)/makesrc.inc
SOURCES = $(SRC)
OBJS := $(SRC:%.c=$(OBJDIR)/%.o)
DIRS = .
ifndef SIMVER
LDS := ../plugin.lds
OUTPUT = $(OUTDIR)/reversi.rock
else ## simulators
OUTPUT = $(OUTDIR)/reversi.rock
endif
all: $(OUTPUT)
ifndef SIMVER
$(OBJDIR)/reversi.elf: $(OBJS) $(LINKFILE) $(BITMAPLIBS)
$(call PRINTS,LD $(@F))$(CC) $(GCCOPTS) -O -nostdlib -o $@ $(OBJS) -L$(BUILDDIR) -lplugin -lgcc \
$(LINKBITMAPS) -T$(LINKFILE) -Wl,-Map,$(OBJDIR)/reversi.map
$(OUTPUT): $(OBJDIR)/reversi.elf
$(call PRINTS,OBJCOPY $(@F))$(OC) -O binary $< $@
else
ifeq ($(SIMVER), x11)
###################################################
# This is the X11 simulator version
$(OUTPUT): $(OBJS)
$(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@
ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
# 'x' must be kept or you'll have "Win32 error 5"
# $ fgrep 5 /usr/include/w32api/winerror.h | head -1
# #define ERROR_ACCESS_DENIED 5L
else
@chmod -x $@
endif
else # end of x11-simulator
ifeq ($(SIMVER), sdl)
###################################################
# This is the SDL simulator version
$(OUTPUT): $(OBJS)
$(call PRINTS,LD $(@F))$(CC) $(CFLAGS) $(SHARED_FLAG) $(OBJS) -L$(BUILDDIR) -lplugin $(LINKBITMAPS) -o $@
ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
# 'x' must be kept or you'll have "Win32 error 5"
# $ fgrep 5 /usr/include/w32api/winerror.h | head -1
# #define ERROR_ACCESS_DENIED 5L
else
@chmod -x $@
endif
else # end of sdl-simulator
###################################################
# This is the win32 simulator version
DLLTOOLFLAGS = --export-all
DLLWRAPFLAGS = -s --entry _DllMain@12 --target=i386-mingw32 -mno-cygwin
$(OUTPUT): $(OBJS)
$(call PRINTS,DLL $(@F))$(DLLTOOL) $(DLLTOOLFLAGS) -z $(OBJDIR)/$*.def $(OBJS)
$(SILENT)$(DLLWRAP) $(DLLWRAPFLAGS) --def $(OBJDIR)/$*.def $(OBJS) \
$(BUILDDIR)/libplugin.a $(BITMAPLIBS) -o $@
ifeq ($(findstring CYGWIN,$(UNAME)),CYGWIN)
# 'x' must be kept or you'll have "Win32 error 5"
# $ fgrep 5 /usr/include/w32api/winerror.h | head -1
# #define ERROR_ACCESS_DENIED 5L
else
@chmod -x $@
endif
endif # end of win32-simulator
endif
endif # end of simulator section
include $(TOOLSDIR)/make.inc
# MEMORYSIZE should be passed on to this makefile with the chosen memory size
# given in number of MB
$(LINKFILE): $(LDS)
$(call PRINTS,build $(@F))cat $< | $(CC) -DMEMORYSIZE=$(MEMORYSIZE) $(INCLUDES) $(TARGET) \
$(DEFINES) -E -P - >$@
clean:
$(call PRINTS,cleaning reversi)rm -rf $(OBJDIR)/reversi
$(SILENT)rm -f $(OBJDIR)/reversi.* $(DEPFILE)
-include $(DEPFILE)

View file

@ -0,0 +1,3 @@
reversi-game.c
reversi-strategy.c
reversi-gui.c

View file

@ -0,0 +1,370 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/*
* Reversi. Code is heavily based on othello code by Claudio Clemens which is
* copyright (c) 2003-2006 Claudio Clemens <asturio at gmx dot net> and is
* released under the GNU General Public License as published by the Free
* Software Foundation; either version 2, or (at your option) any later version.
*/
#include <stddef.h>
#include "reversi-game.h"
/*
* Constants for directions. The values are chosen so that
* they can be bit combined.
*/
#define DIR_UP 1 /* UP */
#define DIR_DO 2 /* DOWN */
#define DIR_LE 4 /* LEFT */
#define DIR_RI 8 /* RIGHT */
#define DIR_UL 16 /* UP LEFT */
#define DIR_UR 32 /* UP RIGHT */
#define DIR_DL 64 /* DOWN LEFT */
#define DIR_DR 128 /* DOWN RIGHT */
/* Array of directions for easy iteration through all of them */
static int DIRECTIONS[] =
{DIR_UP, DIR_DO, DIR_LE, DIR_RI, DIR_UL, DIR_UR, DIR_DL, DIR_DR};
#define NUM_OF_DIRECTIONS 8
/* Initializes a reversi game */
void reversi_init_game(reversi_board_t *game) {
int r, c;
for (r = 0; r < BOARD_SIZE; r++) {
for (c = 0; c < BOARD_SIZE; c++) {
game->board[r][c] = FREE;
}
}
game->board[3][3] = WHITE;
game->board[4][4] = WHITE;
game->board[3][4] = BLACK;
game->board[4][3] = BLACK;
/* Invalidate the history */
c = sizeof(game->history) / sizeof(game->history[0]);
for (r = 0; r < c; r++) {
game->history[r] = MOVE_INVALID;
}
}
/* Returns the 'flipped' color, e.g. WHITE for BLACK and vice versa */
int reversi_flipped_color(const int color) {
switch (color) {
case WHITE:
return BLACK;
case BLACK:
return WHITE;
default:
return FREE;
}
}
/* Counts and returns the number of occupied cells on the board.
* If white_count and/or black_count is not null, the number of
* white/black stones is placed there. */
int reversi_count_occupied_cells(const reversi_board_t *game,
int *white_count, int *black_count) {
int w_cnt, b_cnt, r, c;
w_cnt = b_cnt = 0;
for (r = 0; r < BOARD_SIZE; r++) {
for (c = 0; c < BOARD_SIZE; c++) {
if (game->board[r][c] == WHITE) {
w_cnt++;
} else if (game->board[r][c] == BLACK) {
b_cnt++;
}
}
}
if (white_count != NULL) {
*white_count = w_cnt;
}
if (black_count != NULL) {
*black_count = b_cnt;
}
return w_cnt + b_cnt;
}
/* Returns the number of free cells on the board */
static int reversi_count_free_cells(const reversi_board_t *game) {
int cnt;
cnt = reversi_count_occupied_cells(game, NULL, NULL);
return BOARD_SIZE*BOARD_SIZE - cnt;
}
/* Checks whether the game is finished. That means that nobody
* can make a move. Note that the implementation is not correct
* as a game may be finished even if there are free cells
*/
bool reversi_game_is_finished(const reversi_board_t *game) {
return (reversi_count_free_cells(game) == 0);
}
/* Finds out who should place the next stone. It's the partner
* of the last move or WHITE if the game is just started.
*
* Returns WHITE or BLACK.
*/
int reversi_get_turn(const reversi_board_t *game) {
int moves;
moves = reversi_count_moves(game);
if (moves == 0) {
return WHITE;
} else {
return reversi_flipped_color(MOVE_PLAYER(game->history[moves-1]));
}
}
/* Returns the total number of moves made so far */
int reversi_count_moves(const reversi_board_t *game) {
int cnt;
cnt = reversi_count_occupied_cells(game, NULL, NULL);
return cnt - INIT_STONES;
}
/* Returns the number of moves made by the specified
* player (WHITE or BLACK) so far
*/
static int reversi_count_player_moves(const reversi_board_t *game,
const int player) {
int moves, cnt, i;
moves = reversi_count_moves(game);
cnt = 0;
for (i = 0; i < moves; i++) {
if (MOVE_PLAYER(game->history[i]) == player) {
cnt++;
}
}
return cnt;
}
/* Returns the number of moves made by WHITE so far */
int reversi_count_white_moves(const reversi_board_t *game) {
return reversi_count_player_moves(game, WHITE);
}
/* Returns the number of moves made by BLACK so far */
int reversi_count_black_moves(const reversi_board_t *game) {
return reversi_count_player_moves(game, BLACK);
}
/* Checks whether the specified position is on the board
* (and not beyond)
*/
static bool reversi_is_position_on_board(const int row, const int col) {
return (row >= 0) && (row < BOARD_SIZE) &&
(col >= 0) && (col < BOARD_SIZE);
}
/* Returns the delta for row to move in the specified direction */
static int reversi_row_delta(const int direction) {
switch (direction) {
case DIR_UP:
case DIR_UL:
case DIR_UR:
return -1;
case DIR_DO:
case DIR_DL:
case DIR_DR:
return 1;
default:
return 0;
}
}
/* Returns the delta for column to move in the specified direction */
static int reversi_column_delta(const int direction) {
switch (direction) {
case DIR_LE:
case DIR_UL:
case DIR_DL:
return -1;
case DIR_RI:
case DIR_UR:
case DIR_DR:
return 1;
default:
return 0;
}
}
/* Checks if some stones would be captured in the specified direction
* if a stone were placed in the specified cell by the specified player.
*
* Returns 0 if no stones would be captured or 'direction' otherwise
*/
static int reversi_is_valid_direction(const reversi_board_t *game,
const int row, const int col, const int player, const int direction) {
int row_delta, col_delta, r, c;
int other_color;
int flip_cnt; /* Number of stones that would be flipped */
row_delta = reversi_row_delta(direction);
col_delta = reversi_column_delta(direction);
other_color = reversi_flipped_color(player);
r = row + row_delta;
c = col + col_delta;
flip_cnt = 0;
while (reversi_is_position_on_board(r, c) &&
(game->board[r][c] == other_color)) {
r += row_delta;
c += col_delta;
flip_cnt++;
}
if ((flip_cnt > 0) && reversi_is_position_on_board(r, c) &&
(game->board[r][c] == player)) {
return direction;
} else {
return 0;
}
}
/* Checks whether the move at the specified position would be valid.
* Params:
* - game: current state of the game
* - row: 0-based row number of the move in question
* - col: 0-based column number of the move in question
* - player: who is about to make the move (WHITE/BLACK)
*
* Checks if the place is empty, the coordinates are legal,
* and some stones can be captured.
*
* Returns 0 if the move is not valid or, otherwise, the or'd
* directions in which stones would be captured.
*/
static int reversi_is_valid_move(const reversi_board_t *game,
const int row, const int col, const int player) {
int dirs, i;
dirs = 0;
/* Check if coordinates are legal */
if (!reversi_is_position_on_board(row, col)) {
return dirs;
}
/* Check if the place is free */
if (game->board[row][col] != FREE) {
return dirs;
}
/* Check the directions of capture */
for (i = 0; i < NUM_OF_DIRECTIONS; i++) {
dirs |= reversi_is_valid_direction(game, row, col, player, DIRECTIONS[i]);
}
return dirs;
}
/* Flips the stones in the specified direction after the specified
* player has placed a stone in the specified cell. The move is
* assumed to be valid.
*
* Returns the number of flipped stones in that direction
*/
static int reversi_flip_stones(reversi_board_t *game,
const int row, const int col, const int player, const int direction) {
int row_delta, col_delta, r, c;
int other_color;
int flip_cnt; /* Number of stones flipped */
row_delta = reversi_row_delta(direction);
col_delta = reversi_column_delta(direction);
other_color = reversi_flipped_color(player);
r = row + row_delta;
c = col + col_delta;
flip_cnt = 0;
while (reversi_is_position_on_board(r, c) &&
(game->board[r][c] == other_color)) {
game->board[r][c] = player;
r += row_delta;
c += col_delta;
flip_cnt++;
}
return flip_cnt;
}
/* Tries to make a move (place a stone) at the specified position.
* If the move is valid the board is changed. Otherwise nothing happens.
*
* Params:
* - game: current state of the game
* - row: 0-based row number of the move to make
* - col: 0-based column number of the move to make
* - player: who makes the move (WHITE/BLACK)
*
* Returns the number of flipped (captured) stones (>0) iff the move
* was valid or 0 if the move was not valid. Note that in the case of
* a valid move, the stone itself is not counted.
*/
int reversi_make_move(reversi_board_t *game,
const int row, const int col, const int player) {
int dirs, cnt, i;
dirs = reversi_is_valid_move(game, row, col, player);
if (dirs == 0) {
return 0;
}
/* Place the stone into the cell */
game->board[row][col] = player;
/* Capture stones in all possible directions */
cnt = 0;
for (i = 0; i < NUM_OF_DIRECTIONS; i++) {
if (dirs & DIRECTIONS[i]) {
cnt += reversi_flip_stones(game, row, col, player, DIRECTIONS[i]);
}
}
/* Remember the move */
i = reversi_count_moves(game);
game->history[i-1] = MAKE_MOVE(row, col, player);
return cnt;
}

View file

@ -0,0 +1,75 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _REVERSI_GAME_H
#define _REVERSI_GAME_H
#include <stdbool.h>
#define WHITE 1 /* WHITE constant, it always plays first (as in chess) */
#define BLACK 2 /* BLACK constant */
#define FREE 0 /* Free place constant */
#define BOARD_SIZE 8
#define INIT_STONES 4
/* Description of a move. A move is stored as a byte in the following format:
* - bit 7 : 0 for valid entries (i.e. those containing a move info,
* 1 for invalid entries
* - bits 6..4: row
* - bit 3 : 0 if it's white move, 1 if it's black move
* - bits 2..0: column
*/
typedef unsigned char move_t;
#define MOVE_ROW(h) (((h) >> 4) & 0x7)
#define MOVE_COL(h) ((h) & 0x7)
#define MOVE_PLAYER(h) (((h) & 0x8) ? BLACK : WHITE)
#define MAKE_MOVE(r,c,player) ( ((r)<<4) | ((c)&0x7) | \
((player) == WHITE ? 0 : 0x8) )
#define MOVE_INVALID 0x80
/* State of a board */
typedef struct _reversi_board_t {
/* The current state of the game (BLACK/WHITE/FREE) */
char board[BOARD_SIZE][BOARD_SIZE];
/* Game history. First move (mostly, but not necessarily, black) is stored
* in history[0], second move (mostly, but not necessarily, white) is
* stored in history[1] etc.
*/
move_t history[BOARD_SIZE*BOARD_SIZE - INIT_STONES];
} reversi_board_t;
void reversi_init_game(reversi_board_t *game);
int reversi_flipped_color(const int color);
bool reversi_game_is_finished(const reversi_board_t *game);
int reversi_get_turn(const reversi_board_t *game);
int reversi_count_occupied_cells(const reversi_board_t *game,
int *white_count, int *black_count);
int reversi_count_moves(const reversi_board_t *game);
int reversi_count_white_moves(const reversi_board_t *game);
int reversi_count_black_moves(const reversi_board_t *game);
int reversi_make_move(reversi_board_t *game, const int row,
const int col, const int player);
#endif

View file

@ -0,0 +1,669 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
/*
GUI part of reversi. Code is inspired by sudoku code by Dave Chapman
which is copyright (c) 2005 Dave Chapman and is released under the
GNU General Public License.
User instructions
-----------------
Use the arrow keys to move cursor, and press TOGGLE to place a stone.
At any time during the game, press MENU to bring up the game menu with
further options:
- Save
- Reload
- Clear
*/
#include "plugin.h"
#ifdef HAVE_LCD_BITMAP
#include "reversi-game.h"
#include "reversi-strategy.h"
#include "reversi-gui.h"
#include "../lib/oldmenuapi.h"
PLUGIN_HEADER
/* The global api struct pointer. While not strictly necessary,
it's nice not to have to pass the api pointer in all function
calls in the plugin */
static struct plugin_api* rb;
/* Thickness of the grid lines */
#define LINE_THCK 1
#if LCD_HEIGHT <= LCD_WIDTH /* Horizontal layout */
#if (LCD_HEIGHT==64) && (LCD_WIDTH==112)
/* Archos Recorders and Ondios - 112x64, 8 cells @ 8x6 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 8
#define CELL_HEIGHT 6
#define SMALL_BOARD
#elif (LCD_HEIGHT==110) && (LCD_WIDTH==138)
/* iPod Mini - 138x110, 8 cells @ 10x10 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 10
#define CELL_HEIGHT 10
#elif (LCD_HEIGHT==128) && (LCD_WIDTH==128)
/* iriver H10 5-6GB - 128x128, 8 cells @ 10x10 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 10
#define CELL_HEIGHT 10
#elif ((LCD_HEIGHT==128) && (LCD_WIDTH==160)) || \
((LCD_HEIGHT==132) && (LCD_WIDTH==176))
/* iAudio X5, Iriver H1x0, iPod G3, G4 - 160x128; */
/* iPod Nano - 176x132, 8 cells @ 12x12 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 12
#define CELL_HEIGHT 12
#elif ((LCD_HEIGHT==176) && (LCD_WIDTH==220)) || \
((LCD_HEIGHT==220) && (LCD_WIDTH==176))
/* Iriver h300, iPod Color/Photo - 220x176, 8 cells @ 16x16 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 16
#define CELL_HEIGHT 16
#elif (LCD_HEIGHT>=240) && (LCD_WIDTH>=320)
/* iPod Video - 320x240, 8 cells @ 24x24 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 24
#define CELL_HEIGHT 24
#else
#error REVERSI: Unsupported LCD size
#endif
#else /* Vertical layout */
#define VERTICAL_LAYOUT
#if (LCD_HEIGHT>=320) && (LCD_WIDTH>=240)
/* Gigabeat - 240x320, 8 cells @ 24x24 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 24
#define CELL_HEIGHT 24
#elif (LCD_HEIGHT>=220) && (LCD_WIDTH>=176)
/* e200 - 176x220, 8 cells @ 12x12 with 9 border lines */
/* Internal dimensions of a cell */
#define CELL_WIDTH 18
#define CELL_HEIGHT 18
#else
#error REVERSI: Unsupported LCD size
#endif
#endif /* Layout */
/* Where the board begins */
#define XOFS 4
#define YOFS 4
/* Total width and height of the board without enclosing box */
#define BOARD_WIDTH (CELL_WIDTH*BOARD_SIZE + LINE_THCK*(BOARD_SIZE+1))
#define BOARD_HEIGHT (CELL_HEIGHT*BOARD_SIZE + LINE_THCK*(BOARD_SIZE+1))
/* Thickness of the white cells' lines */
#if (CELL_WIDTH >= 15) && (CELL_HEIGHT >= 15)
#define CELL_LINE_THICKNESS 2
#else
#define CELL_LINE_THICKNESS 1
#endif
/* Margins within a cell */
#if (CELL_WIDTH >= 10) && (CELL_HEIGHT >= 10)
#define STONE_MARGIN 2
#else
#define STONE_MARGIN 1
#endif
#define CURSOR_MARGIN (STONE_MARGIN + CELL_LINE_THICKNESS)
/* Upper left corner of a cell */
#define CELL_X(c) (XOFS + (c)*CELL_WIDTH + ((c)+1)*LINE_THCK)
#define CELL_Y(r) (YOFS + (r)*CELL_HEIGHT + ((r)+1)*LINE_THCK)
#ifdef VERTICAL_LAYOUT
#define LEGEND_X(lc) (CELL_X(lc))
#define LEGEND_Y(lr) (CELL_Y(BOARD_SIZE+(lr)) + CELL_HEIGHT/2)
#else
#define LEGEND_X(lc) (CELL_X(BOARD_SIZE+(lc)) + CELL_WIDTH/2)
#define LEGEND_Y(lr) (CELL_Y(lr))
#endif
/* Board state */
static reversi_board_t game;
/* --- Setting values --- */
/* Playing strategies used by white and black players */
const game_strategy_t *white_strategy;
const game_strategy_t *black_strategy;
/* Cursor position */
static int cur_row, cur_col;
/* Color for the next move (BLACK/WHITE) */
static int cur_player;
/* Active cursor wrapping mode */
static cursor_wrap_mode_t cursor_wrap_mode;
static bool quit_plugin;
/* Initialises the state of the game (starts a new game) */
static void reversi_gui_init(void) {
reversi_init_game(&game);
white_strategy = &strategy_human;
black_strategy = &strategy_human;
cur_player = BLACK;
/* Place the cursor so that WHITE can make a move */
cur_row = 2;
cur_col = 3;
}
/* Draws the cursor in the specified cell. Cursor is drawn in the complement
* mode, i.e. drawing it twice will result in no changes on the screen.
*/
static void reversi_gui_display_cursor(int row, int col) {
int old_mode, x, y;
old_mode = rb->lcd_get_drawmode();
x = CELL_X(col);
y = CELL_Y(row);
rb->lcd_set_drawmode(DRMODE_COMPLEMENT);
rb->lcd_drawline(x+CURSOR_MARGIN, y+CURSOR_MARGIN,
x+CELL_WIDTH-CURSOR_MARGIN-1, y+CELL_HEIGHT-CURSOR_MARGIN-1);
rb->lcd_drawline(x+CURSOR_MARGIN, y+CELL_HEIGHT-CURSOR_MARGIN-1,
x+CELL_WIDTH-CURSOR_MARGIN-1, y+CURSOR_MARGIN);
/* Draw the shadows */
rb->lcd_hline(x, x+CELL_WIDTH-1, YOFS-3);
rb->lcd_hline(x, x+CELL_WIDTH-1, YOFS+BOARD_HEIGHT+2);
rb->lcd_vline(XOFS-3, y, y+CELL_HEIGHT-1);
rb->lcd_vline(XOFS+BOARD_WIDTH+2, y, y+CELL_HEIGHT-1);
rb->lcd_set_drawmode(old_mode);
rb->lcd_update();
}
/* Draws the cell of the specified color (WHITE/BLACK) assuming that
* the upper left corner of the cell is at (x, y) */
static void reversi_gui_draw_cell(int x, int y, int color) {
int i;
if (color == WHITE) {
for (i = 0; i < CELL_LINE_THICKNESS; i++) {
rb->lcd_drawrect(x+STONE_MARGIN+i, y+STONE_MARGIN+i,
CELL_WIDTH-2*(STONE_MARGIN+i), CELL_HEIGHT-2*(STONE_MARGIN+i));
}
} else if (color == BLACK) {
rb->lcd_fillrect(x+STONE_MARGIN, y+STONE_MARGIN,
CELL_WIDTH-2*STONE_MARGIN, CELL_HEIGHT-2*STONE_MARGIN);
} else {
/* Cell is free -> nothing to do */
}
}
/* Draws the complete screen */
static void reversi_gui_display_board(void) {
int x, y, r, c, x_width, x_height;
char buf[8];
/* Clear the display buffer */
rb->lcd_clear_display();
rb->lcd_set_drawmode(DRMODE_FG);
/* Thicker board box */
rb->lcd_drawrect(XOFS-1, YOFS-1, BOARD_WIDTH+2, BOARD_HEIGHT+2);
/* Draw the gridlines */
for (r=0, x=XOFS, y=YOFS; r<=BOARD_SIZE;
r++, x+=CELL_WIDTH+LINE_THCK, y+=CELL_HEIGHT+LINE_THCK) {
rb->lcd_hline(XOFS, XOFS+BOARD_WIDTH-1, y);
rb->lcd_vline(x, YOFS, YOFS+BOARD_HEIGHT-1);
}
/* Draw the stones. This is not the most efficient way but more readable */
for (r=0; r<BOARD_SIZE; r++) {
y = CELL_Y(r);
for (c=0; c<BOARD_SIZE; c++) {
x = CELL_X(c);
reversi_gui_draw_cell(x, y, game.board[r][c]);
}
}
/* Draw the cursor */
reversi_gui_display_cursor(cur_row, cur_col);
/* Draw the current score */
reversi_count_occupied_cells(&game, &r, &c);
rb->lcd_getstringsize("x", &x_width, &x_height);
x = LEGEND_X(0);
y = LEGEND_Y(0);
reversi_gui_draw_cell(x, y, BLACK);
rb->snprintf(buf, sizeof(buf), "%d", c);
y += (CELL_HEIGHT-x_height) / 2;
rb->lcd_putsxy(x + CELL_WIDTH + CELL_WIDTH/2, y, buf);
y = LEGEND_Y(1);
reversi_gui_draw_cell(x, y, WHITE);
rb->snprintf(buf, sizeof(buf), "%d", r);
y += (CELL_HEIGHT-x_height) / 2;
rb->lcd_putsxy(x + CELL_WIDTH + CELL_WIDTH/2, y, buf);
/* Draw the box around the current player */
r = (cur_player == BLACK ? 0 : 1);
y = LEGEND_Y(r);
rb->lcd_drawrect(x-1, y-1, CELL_WIDTH+2, CELL_HEIGHT+2);
/* Update the screen */
rb->lcd_update();
}
/*
* Menu related stuff
*/
/* Menu entries and the corresponding values for cursor wrap mode */
#define MENU_TEXT_WRAP_MODE "Cursor wrap mode"
static const struct opt_items cursor_wrap_mode_settings[] = {
{ "Flat board", NULL },
{ "Sphere", NULL },
{ "Torus", NULL },
};
static const cursor_wrap_mode_t cursor_wrap_mode_values[3] = {
WRAP_FLAT, WRAP_SPHERE, WRAP_TORUS };
/* Menu entries and the corresponding values for available strategies */
#define MENU_TEXT_STRAT_WHITE "Strategy for white"
#define MENU_TEXT_STRAT_BLACK "Strategy for black"
static struct opt_items strategy_settings[] = {
{ "Human", NULL },
{ "Silly robot", NULL },
{ "Smart robot", NULL },
};
static const game_strategy_t * const strategy_values[] = {
&strategy_human, &strategy_novice, &strategy_expert };
/* Sets the strategy for the specified player. 'player' is the
pointer to the player to set the strategy for (actually,
either white_strategy or black_strategy). propmpt is the
text to show as the prompt in the menu */
static bool reversi_gui_choose_strategy(
const game_strategy_t **player, const char *prompt) {
int index = 0, i;
int num_items = sizeof(strategy_settings)/sizeof(strategy_settings[0]);
bool result;
for (i = 0; i < num_items; i++) {
if ((*player) == strategy_values[i]) {
index = i;
break;
}
}
result = rb->set_option(prompt, &index, INT, strategy_settings, num_items, NULL);
(*player) = strategy_values[index];
return result;
}
/* Returns true iff USB ws connected while in the menu */
static bool reversi_gui_menu(void) {
int m, index, num_items, i;
int result;
static const struct menu_item items[] = {
{ "Start new game", NULL },
{ "Pass the move", NULL },
{ MENU_TEXT_STRAT_BLACK, NULL },
{ MENU_TEXT_STRAT_WHITE, NULL },
{ MENU_TEXT_WRAP_MODE, NULL },
{ "Quit", NULL },
};
m = menu_init(rb, items, sizeof(items) / sizeof(*items),
NULL, NULL, NULL, NULL);
result = menu_show(m);
switch (result) {
case 0: /* Start a new game */
reversi_gui_init();
break;
case 1: /* Pass the move to the partner */
cur_player = reversi_flipped_color(cur_player);
break;
case 2: /* Strategy for black */
reversi_gui_choose_strategy(&black_strategy, MENU_TEXT_STRAT_BLACK);
break;
case 3: /* Strategy for white */
reversi_gui_choose_strategy(&white_strategy, MENU_TEXT_STRAT_WHITE);
break;
case 4: /* Cursor wrap mode */
num_items = sizeof(cursor_wrap_mode_values)/sizeof(cursor_wrap_mode_values[0]);
index = 0;
for (i = 0; i < num_items; i++) {
if (cursor_wrap_mode == cursor_wrap_mode_values[i]) {
index = i;
break;
}
}
rb->set_option(MENU_TEXT_WRAP_MODE, &index, INT,
cursor_wrap_mode_settings, 3, NULL);
cursor_wrap_mode = cursor_wrap_mode_values[index];
break;
case 5: /* Quit */
quit_plugin = true;
break;
}
menu_exit(m);
return (result == MENU_ATTACHED_USB);
}
/* Calculates the new cursor position if the user wants to move it
* vertically as specified by delta. Current wrap mode is respected.
* The cursor is not actually moved.
*
* Returns true iff the cursor would be really moved. In any case, the
* new cursor position is stored in (new_row, new_col).
*/
static bool reversi_gui_cursor_pos_vmove(int row_delta, int *new_row, int *new_col) {
*new_row = cur_row + row_delta;
*new_col = cur_col;
if (*new_row < 0) {
switch (cursor_wrap_mode) {
case WRAP_FLAT:
*new_row = cur_row;
break;
case WRAP_SPHERE:
*new_row = BOARD_SIZE - 1;
break;
case WRAP_TORUS:
*new_row = BOARD_SIZE - 1;
(*new_col)--;
if (*new_col < 0) {
*new_col = BOARD_SIZE - 1;
}
break;
}
} else if (*new_row >= BOARD_SIZE) {
switch (cursor_wrap_mode) {
case WRAP_FLAT:
*new_row = cur_row;
break;
case WRAP_SPHERE:
*new_row = 0;
break;
case WRAP_TORUS:
*new_row = 0;
(*new_col)++;
if (*new_col >= BOARD_SIZE) {
*new_col = 0;
}
break;
}
}
return (cur_row != (*new_row)) || (cur_col != (*new_col));
}
/* Calculates the new cursor position if the user wants to move it
* horisontally as specified by delta. Current wrap mode is respected.
* The cursor is not actually moved.
*
* Returns true iff the cursor would be really moved. In any case, the
* new cursor position is stored in (new_row, new_col).
*/
static bool reversi_gui_cursor_pos_hmove(int col_delta, int *new_row, int *new_col) {
*new_row = cur_row;
*new_col = cur_col + col_delta;
if (*new_col < 0) {
switch (cursor_wrap_mode) {
case WRAP_FLAT:
*new_col = cur_col;
break;
case WRAP_SPHERE:
*new_col = BOARD_SIZE - 1;
break;
case WRAP_TORUS:
*new_col = BOARD_SIZE - 1;
(*new_row)--;
if (*new_row < 0) {
*new_row = BOARD_SIZE - 1;
}
break;
}
} else if (*new_col >= BOARD_SIZE) {
switch (cursor_wrap_mode) {
case WRAP_FLAT:
*new_col = cur_col;
break;
case WRAP_SPHERE:
*new_col = 0;
break;
case WRAP_TORUS:
*new_col = 0;
(*new_row)++;
if (*new_row >= BOARD_SIZE) {
*new_row = 0;
}
break;
}
}
return (cur_row != (*new_row)) || (cur_col != (*new_col));
}
/* Actually moves the cursor to the new position and updates the screen */
static void reversi_gui_move_cursor(int new_row, int new_col) {
int old_row, old_col;
old_row = cur_row;
old_col = cur_col;
cur_row = new_row;
cur_col = new_col;
/* Only update the changed cells since there are no global changes */
reversi_gui_display_cursor(old_row, old_col);
reversi_gui_display_cursor(new_row, new_col);
}
/* plugin entry point */
enum plugin_status plugin_start(struct plugin_api *api, void *parameter) {
bool exit, draw_screen;
int button;
int lastbutton = BUTTON_NONE;
int row, col;
int w_cnt, b_cnt;
char msg_buf[30];
/* plugin init */
rb = api;
/* end of plugin init */
#if LCD_DEPTH > 1
rb->lcd_set_backdrop(NULL);
rb->lcd_set_foreground(LCD_BLACK);
rb->lcd_set_background(LCD_WHITE);
#endif
/* Avoid compiler warnings */
(void)parameter;
reversi_gui_init();
cursor_wrap_mode = WRAP_FLAT;
/* The main game loop */
exit = false;
quit_plugin = false;
draw_screen = true;
while (!exit && !quit_plugin) {
if (draw_screen) {
reversi_gui_display_board();
draw_screen = false;
}
button = rb->button_get(true);
switch (button) {
#ifdef REVERSI_BUTTON_QUIT
/* Exit game */
case REVERSI_BUTTON_QUIT:
exit = true;
break;
#endif
#ifdef REVERSI_BUTTON_ALT_MAKE_MOVE
case REVERSI_BUTTON_ALT_MAKE_MOVE:
#endif
case REVERSI_BUTTON_MAKE_MOVE:
#ifdef REVERSI_BUTTON_MAKE_MOVE_PRE
if ((button == REVERSI_BUTTON_MAKE_MOVE)
&& (lastbutton != REVERSI_BUTTON_MAKE_MOVE_PRE))
break;
#endif
if (reversi_make_move(&game, cur_row, cur_col, cur_player) > 0) {
/* Move was made. Global changes on the board are possible */
draw_screen = true; /* Redraw the screen next time */
cur_player = reversi_flipped_color(cur_player);
if (reversi_game_is_finished(&game)) {
reversi_count_occupied_cells(&game, &w_cnt, &b_cnt);
rb->snprintf(msg_buf, sizeof(msg_buf),
"Game over. %s have won.",
(w_cnt>b_cnt?"WHITE":"BLACK"));
rb->splash(HZ*2, msg_buf);
draw_screen = true; /* Must update screen after splash */
}
} else {
/* An attempt to make an invalid move */
rb->splash(HZ/2, "Illegal move!");
draw_screen = true;
/* Ignore any button presses during the splash */
rb->button_clear_queue();
}
break;
/* Move cursor left */
case REVERSI_BUTTON_LEFT:
case (REVERSI_BUTTON_LEFT | BUTTON_REPEAT):
if (reversi_gui_cursor_pos_hmove(-1, &row, &col)) {
reversi_gui_move_cursor(row, col);
}
break;
/* Move cursor right */
case REVERSI_BUTTON_RIGHT:
case (REVERSI_BUTTON_RIGHT | BUTTON_REPEAT):
if (reversi_gui_cursor_pos_hmove(1, &row, &col)) {
reversi_gui_move_cursor(row, col);
}
break;
/* Move cursor up */
case REVERSI_BUTTON_UP:
case (REVERSI_BUTTON_UP | BUTTON_REPEAT):
if (reversi_gui_cursor_pos_vmove(-1, &row, &col)) {
reversi_gui_move_cursor(row, col);
}
break;
/* Move cursor down */
case REVERSI_BUTTON_DOWN:
case (REVERSI_BUTTON_DOWN | BUTTON_REPEAT):
if (reversi_gui_cursor_pos_vmove(1, &row, &col)) {
reversi_gui_move_cursor(row, col);
}
break;
case REVERSI_BUTTON_MENU:
#ifdef REVERSI_BUTTON_MENU_PRE
if (lastbutton != REVERSI_BUTTON_MENU_PRE) {
break;
}
#endif
if (reversi_gui_menu()) {
return PLUGIN_USB_CONNECTED;
}
draw_screen = true;
break;
default:
if (rb->default_event_handler(button) == SYS_USB_CONNECTED) {
/* Quit if USB has been connected */
return PLUGIN_USB_CONNECTED;
}
break;
}
if (button != BUTTON_NONE) {
lastbutton = button;
}
}
return PLUGIN_OK;
}
#endif

View file

@ -0,0 +1,117 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _REVERSI_GUI_H
#define _REVERSI_GUI_H
#include "plugin.h"
#define GAME_FILE PLUGIN_DIR "/reversi.rev"
/* variable button definitions */
#if CONFIG_KEYPAD == RECORDER_PAD
#define REVERSI_BUTTON_QUIT BUTTON_OFF
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_PLAY
#define REVERSI_BUTTON_MENU BUTTON_F1
#elif CONFIG_KEYPAD == ONDIO_PAD
#define REVERSI_BUTTON_QUIT BUTTON_OFF
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE_PRE BUTTON_MENU
#define REVERSI_BUTTON_MAKE_MOVE (BUTTON_MENU | BUTTON_REL)
#define REVERSI_BUTTON_ALT_MAKE_MOVE (BUTTON_MENU | BUTTON_DOWN)
#define REVERSI_BUTTON_MENU_PRE BUTTON_MENU
#define REVERSI_BUTTON_MENU (BUTTON_MENU | BUTTON_REPEAT)
#elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
(CONFIG_KEYPAD == IRIVER_H300_PAD)
#define REVERSI_BUTTON_QUIT BUTTON_OFF
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT
#define REVERSI_BUTTON_MENU BUTTON_MODE
#elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
(CONFIG_KEYPAD == IPOD_3G_PAD)
#define REVERSI_BUTTON_UP BUTTON_SCROLL_BACK
#define REVERSI_BUTTON_DOWN BUTTON_SCROLL_FWD
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT
#define REVERSI_BUTTON_MENU BUTTON_MENU
#elif (CONFIG_KEYPAD == IAUDIO_X5_PAD)
#define REVERSI_BUTTON_QUIT BUTTON_POWER
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT
#define REVERSI_BUTTON_MENU BUTTON_PLAY
#elif (CONFIG_KEYPAD == GIGABEAT_PAD)
#define REVERSI_BUTTON_QUIT BUTTON_A
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT
#define REVERSI_BUTTON_MENU BUTTON_MENU
#elif (CONFIG_KEYPAD == IRIVER_H10_PAD)
#define REVERSI_BUTTON_QUIT BUTTON_POWER
#define REVERSI_BUTTON_UP BUTTON_SCROLL_UP
#define REVERSI_BUTTON_DOWN BUTTON_SCROLL_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_REW
#define REVERSI_BUTTON_MENU BUTTON_PLAY
#elif (CONFIG_KEYPAD == SANSA_E200_PAD)
#define REVERSI_BUTTON_QUIT BUTTON_POWER
#define REVERSI_BUTTON_UP BUTTON_UP
#define REVERSI_BUTTON_DOWN BUTTON_DOWN
#define REVERSI_BUTTON_LEFT BUTTON_LEFT
#define REVERSI_BUTTON_RIGHT BUTTON_RIGHT
#define REVERSI_BUTTON_MAKE_MOVE BUTTON_SELECT
#define REVERSI_BUTTON_MENU (BUTTON_SELECT|BUTTON_REPEAT)
#elif
#error REVERSI: Unsupported keypad
#endif
/* Modes for the cursor behaviour at the board edges */
typedef enum _cursor_wrap_mode_t {
WRAP_FLAT, /* No wrapping */
WRAP_SPHERE, /* (7,7) > right > (7,0); (7,7) > down > (0,7) */
WRAP_TORUS, /* (7,7) > right > (0,0); (7,7) > down > (0,0) */
} cursor_wrap_mode_t;
#endif

View file

@ -0,0 +1,59 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include "reversi-strategy.h"
#include <stddef.h>
/* Implementation of a rather weak player strategy */
static move_t novice_move_func(const reversi_board_t *game, int color) {
/* TODO: Implement novice_move_func */
(void)game;
(void)color;
return MOVE_INVALID;
}
/* Implementation of a good player strategy */
static move_t expert_move_func(const reversi_board_t *game, int color) {
/* TODO: Implement expert_move_func */
(void)game;
(void)color;
return MOVE_INVALID;
}
/* Strategy that requires interaction with the user */
const game_strategy_t strategy_human = {
false,
NULL
};
/* Robot that plays not very well (novice) */
const game_strategy_t strategy_novice = {
true,
novice_move_func
};
/* Robot that is hard to beat (expert) */
const game_strategy_t strategy_expert = {
true,
expert_move_func
};

View file

@ -0,0 +1,44 @@
/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (c) 2006 Alexander Levin
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#ifndef _REVERSI_STRATEGY_H
#define _REVERSI_STRATEGY_H
#include "reversi-game.h"
/* Function for making a move. Is called only for robots.
Should return the game history entry for the advised move if
a move has been considered or HISTORY_INVALID_ENTRY if no move
has been considered. The board should not be modified. */
typedef move_t (*move_func_t)(const reversi_board_t *game, int color);
/* A playing party/strategy */
typedef struct _game_strategy_t {
const bool is_robot; /* Is the player a robot or does it require user input? */
move_func_t move_func; /* Function for advicing a move */
} game_strategy_t;
/* --- Possible playing strategies --- */
extern const game_strategy_t strategy_human;
extern const game_strategy_t strategy_novice;
extern const game_strategy_t strategy_expert;
#endif