/* gcompris - photohunter.c
*
* Copyright (C) 2009 Marc Le Douarain
*
* 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 3 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, see .
*/
#include
#include "gcompris/gcompris.h"
static GcomprisBoard *gcomprisBoard = NULL;
static GooCanvasItem *boardRootItem = NULL;
static gboolean board_paused = TRUE;
static void start_board (GcomprisBoard *agcomprisBoard);
static void pause_board (gboolean pause);
static void end_board (void);
static void set_level(guint level);
static gboolean is_our_board (GcomprisBoard *gcomprisBoard);
static gboolean increment_sublevel(void);
//#define GAME_DEBUG
#define FRAME_OFFSET 2
typedef struct
{
int PhotoWidth;
int PhotoHeight;
int SpaceX;
int SpaceY;
} StructGame;
#define POINT_SIZE 4
typedef guchar Pixel[POINT_SIZE];
GooCanvasItem *ItemPhoto[2] = {NULL, NULL };
GooCanvasItem *ItemFrame[2] = {NULL, NULL };
GArray *gDiffCoorArray = NULL; // coords list of the differences in the photos
GArray *gDiffFoundArray = NULL; // coords already discovered
StructGame Game;
int LoadNextLevelAfterEndOfBonusDisplay;
static void CleanLevelDatas( void );
static void StartLevel( );
static gint MouseClick(GooCanvasItem *item, GooCanvasItem *target,
GdkEvent *event, gpointer data);
/* Description of this plugin */
static BoardPlugin menu_bp =
{
NULL,
NULL,
"Photo hunter",
"Find differences between two photos",
"Marc Le Douarain (http://membres.lycos.fr/mavati)",
NULL,
NULL,
NULL,
NULL,
start_board,
pause_board,
end_board,
is_our_board,
NULL,
NULL,
set_level,
NULL,
NULL,
NULL,
NULL
};
/* =====================================================================
* IMPORTANT, REQUIRED TO "SEE" THE PLUGIN BOARD !
* =====================================================================*/
GET_BPLUGIN_INFO(photohunter)
/* =====================================================================
* (ALSO CALLED AFTER END OF BONUS DISPLAY WHEN A LEVEL IS COMPLETED)
* =====================================================================*/
static void pause_board (gboolean pause)
{
if(gcomprisBoard==NULL)
return;
if ( LoadNextLevelAfterEndOfBonusDisplay==TRUE && pause==FALSE )
{
LoadNextLevelAfterEndOfBonusDisplay = FALSE;
if(increment_sublevel())
StartLevel( );
}
board_paused = pause;
}
/* =====================================================================
*
* =====================================================================*/
static void start_board (GcomprisBoard *agcomprisBoard)
{
if(agcomprisBoard!=NULL)
{
gcomprisBoard = agcomprisBoard;
gc_set_default_background(goo_canvas_get_root_item(gcomprisBoard->canvas));
gcomprisBoard->level = 1;
gcomprisBoard->sublevel = 0;
gcomprisBoard->number_of_sublevel = 0;
/* Calculate the maxlevel based on the available data file for this board */
gcomprisBoard->maxlevel=1;
gchar *filename;
while( (filename = gc_file_find_absolute("%s/board%d_0a.png",
gcomprisBoard->boarddir,
gcomprisBoard->maxlevel,
NULL)) )
{
gcomprisBoard->maxlevel++;
g_free(filename);
}
g_free(filename);
gcomprisBoard->maxlevel--;
/* In this board, the sublevels are dynamicaly discovered based on data files */
gcomprisBoard->number_of_sublevel = G_MAXINT;
gc_bar_set(GC_BAR_LEVEL);
gc_bar_location(-1, -1, 0.5);
g_signal_connect(goo_canvas_get_root_item(gcomprisBoard->canvas),
"button_press_event",
(GCallback) MouseClick, NULL);
boardRootItem = NULL;
gDiffCoorArray = g_array_new( FALSE, FALSE, sizeof(GooCanvasBounds) );
gDiffFoundArray = g_array_new( FALSE, FALSE, sizeof(GooCanvasBounds) );
LoadNextLevelAfterEndOfBonusDisplay = FALSE;
StartLevel( );
pause_board(FALSE);
}
}
/* =====================================================================
*
* =====================================================================*/
static void end_board ()
{
if(gcomprisBoard!=NULL)
{
pause_board(TRUE);
gc_score_end();
}
CleanLevelDatas( );
g_signal_handlers_disconnect_by_func(goo_canvas_get_root_item(gcomprisBoard->canvas),
(GCallback) MouseClick, NULL);
gcomprisBoard = NULL;
if(boardRootItem!=NULL)
goo_canvas_item_remove( boardRootItem );
if ( gDiffCoorArray!=NULL )
g_array_free( gDiffCoorArray, TRUE );
if ( gDiffFoundArray!=NULL )
g_array_free( gDiffFoundArray, TRUE );
}
/* =====================================================================
*
* =====================================================================*/
static void set_level(guint level)
{
if(gcomprisBoard!=NULL)
{
gcomprisBoard->level = level;
gcomprisBoard->sublevel = 0;
if ( gcomprisBoard->level>gcomprisBoard->maxlevel )
gcomprisBoard->level = 1;
StartLevel();
}
}
/* =====================================================================
*
* =====================================================================*/
static gboolean is_our_board (GcomprisBoard *gcomprisBoard)
{
if (gcomprisBoard)
{
if(g_ascii_strcasecmp(gcomprisBoard->type, "photohunter")==0)
{
/* Set the plugin entry */
gcomprisBoard->plugin=&menu_bp;
return TRUE;
}
}
return FALSE;
}
static gboolean increment_sublevel()
{
gcomprisBoard->sublevel++;
if(gcomprisBoard->sublevel>gcomprisBoard->number_of_sublevel) {
/* Try the next level */
gcomprisBoard->level++;
gcomprisBoard->sublevel=0;
if(gcomprisBoard->level>gcomprisBoard->maxlevel) { // the current board is finished : restart
gcomprisBoard->level = 1;
gcomprisBoard->sublevel=0;
}
}
return TRUE;
}
static void CleanLevelDatas( void )
{
int ScanPhoto;
if ( gDiffCoorArray!=NULL )
{
if ( gDiffCoorArray->len>0 )
g_array_remove_range( gDiffCoorArray, 0, gDiffCoorArray->len );
}
if ( gDiffFoundArray!=NULL )
{
if ( gDiffFoundArray->len>0 )
g_array_remove_range( gDiffFoundArray, 0, gDiffFoundArray->len );
}
for( ScanPhoto=0; ScanPhoto<2; ScanPhoto++ )
{
if ( ItemPhoto[ ScanPhoto ]!=NULL )
{
ItemPhoto[ ScanPhoto ] = NULL;
}
if ( ItemFrame[ ScanPhoto ]!=NULL )
{
ItemFrame[ ScanPhoto ] = NULL;
}
}
}
static gboolean
diff_pixel (Pixel p1, Pixel p2)
{
int i;
for (i=0; i= 0 && x < width);
g_assert (y >= 0 && y < height);
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
pixels = gdk_pixbuf_get_pixels (pixbuf);
p = pixels + y * rowstride + x * n_channels;
for( i=0; i= width ||
y < 0 || y >= height)
{
for( i=0; ix1 = MIN(bounds->x1, x);
bounds->y1 = MIN(bounds->y1, y);
bounds->x2 = MAX(bounds->x2, x);
bounds->y2 = MAX(bounds->y2, y);
flood_check(pixbuf1, pixbuf2, x + 1, y, bounds);
flood_check(pixbuf1, pixbuf2, x - 1, y, bounds);
flood_check(pixbuf1, pixbuf2, x, y - 1, bounds);
flood_check(pixbuf1, pixbuf2, x, y + 1, bounds);
flood_check(pixbuf1, pixbuf2, x + 1, y + 1, bounds);
flood_check(pixbuf1, pixbuf2, x + 1, y - 1, bounds);
flood_check(pixbuf1, pixbuf2, x - 1, y - 1, bounds);
flood_check(pixbuf1, pixbuf2, x - 1, y + 1, bounds);
return;
}
static GooCanvasItem * LoadPhoto( GdkPixbuf *pixmap, int PhotoNbr )
{
GooCanvasItem * item = NULL;
if(pixmap)
{
int PosiX,PosiY;
if ( PhotoNbr==0 )
{
Game.PhotoWidth = gdk_pixbuf_get_width( pixmap );
Game.PhotoHeight = gdk_pixbuf_get_height( pixmap );
Game.SpaceX = (BOARDWIDTH-2*Game.PhotoWidth)/3;
Game.SpaceY = (BOARDHEIGHT-Game.PhotoHeight)/2;
PosiX = Game.SpaceX;
PosiY = Game.SpaceY;
}
else
{
PosiX = 2*Game.SpaceX+Game.PhotoWidth;
PosiY = Game.SpaceY;
}
item = goo_canvas_image_new (boardRootItem,
pixmap,
PosiX,
PosiY,
NULL);
}
return item;
}
static void
DrawCircle( int x1, int y1, int x2, int y2, char *color )
{
int wid2=(x2-x1)/2;
int hei2=(y2-y1)/2;
goo_canvas_ellipse_new (boardRootItem,
x1+wid2,
y1+hei2,
wid2,
hei2,
"stroke-color", color,
"line-width", (double)1,
NULL);
}
static GooCanvasItem * DrawPhotoFrame( int PhotoNbr, char * color )
{
int x1, y1;
GooCanvasItem * item = NULL;
if ( PhotoNbr==0 )
{
x1 = Game.SpaceX-FRAME_OFFSET;
y1 = Game.SpaceY-FRAME_OFFSET;
}
else
{
x1 = 2*Game.SpaceX+Game.PhotoWidth-FRAME_OFFSET;
y1 = Game.SpaceY-FRAME_OFFSET;
}
item = goo_canvas_rect_new( boardRootItem,
x1, y1,
Game.PhotoWidth+FRAME_OFFSET +2, Game.PhotoHeight+FRAME_OFFSET +2,
/*"fill_color"*/"stroke_color", color,
NULL );
return item;
}
gchar *get_next_datafile()
{
gchar *filename;
while( ((filename = gc_file_find_absolute("%s/board%d_%da.png",
gcomprisBoard->boarddir,
gcomprisBoard->level,
gcomprisBoard->sublevel,
NULL)) == NULL)
&& ((gcomprisBoard->level != 1) || (gcomprisBoard->sublevel!=0)))
{
/* Try the next level */
gcomprisBoard->sublevel = gcomprisBoard->number_of_sublevel;
if(!increment_sublevel())
{
g_free(filename);
return NULL;
}
}
if ( !filename)
return NULL;
filename[strlen(filename)-5] = '\0';
return filename;
}
void
search_diffs(GdkPixbuf *pixbuf1, GdkPixbuf *pixbuf2)
{
int x, y;
int width, height;
width = gdk_pixbuf_get_width (pixbuf1);
height = gdk_pixbuf_get_height (pixbuf2);
for ( x=0; x 10 )
g_array_append_val( gDiffCoorArray, bounds );
}
}
}
}
static void StartLevel( )
{
{
int ScanPhoto;
char * str;
if(boardRootItem!=NULL)
goo_canvas_item_remove( boardRootItem );
CleanLevelDatas( );
boardRootItem =
goo_canvas_group_new (goo_canvas_get_root_item(gcomprisBoard->canvas), NULL);
// get base filename datas to play
gchar * RandomFileToLoad = get_next_datafile();
if (!RandomFileToLoad)
{
gc_dialog(_("Error: Absolutely no photo found in the data directory"),
gc_board_stop);
return;
}
GdkPixbuf *pixmap[2];
for( ScanPhoto=0; ScanPhoto<2; ScanPhoto++ )
{
str = g_strdup_printf("%s%c.png", RandomFileToLoad,
ScanPhoto==0?'a':'b' );
pixmap[ScanPhoto] = gc_pixmap_load(str);
ItemPhoto[ ScanPhoto ] = LoadPhoto( pixmap[ScanPhoto], ScanPhoto );
g_free(str);
ItemFrame[ ScanPhoto ] = DrawPhotoFrame( ScanPhoto, "black" );
}
search_diffs(pixmap[0], pixmap[1]);
for( ScanPhoto=0; ScanPhoto<2; ScanPhoto++ ) {
#if GDK_PIXBUF_MAJOR <= 2 && GDK_PIXBUF_MINOR <= 24
gdk_pixbuf_unref(pixmap[ScanPhoto]);
#else
g_object_unref(pixmap[ScanPhoto]);
#endif
}
g_free(RandomFileToLoad);
#ifdef GAME_DEBUG
int scanposi;
for( scanposi=0; scanposilen; scanposi++)
{
GooCanvasBounds *pDiff = \
&g_array_index (gDiffCoorArray, GooCanvasBounds, scanposi);
printf("diff %d (%lf %lf) (%lf %lf)\n", scanposi,
pDiff->x1, pDiff->y1,
pDiff->x2, pDiff->y2);
DrawCircle( Game.SpaceX+pDiff->x1, Game.SpaceY+pDiff->y1,
Game.SpaceX+pDiff->x2, Game.SpaceY+pDiff->y2,
"green" );
DrawCircle( (2*Game.SpaceX+Game.PhotoWidth)+pDiff->x1,
Game.SpaceY+pDiff->y1,
(2*Game.SpaceX+Game.PhotoWidth)+pDiff->x2,
Game.SpaceY+pDiff->y2,
"green" );
}
#endif
gc_score_start(SCORESTYLE_NOTE,
BOARDWIDTH - 195,
BOARDHEIGHT - 30,
gDiffCoorArray->len);
gc_score_set(0);
gc_bar_set_level(gcomprisBoard);
if (gcomprisBoard->level == 1 && gcomprisBoard->sublevel == 0)
{
GooCanvasItem *item =
goo_canvas_svg_new (boardRootItem,
gc_skin_rsvg_get(),
"svg-id", "#BAR_BG",
NULL);
SET_ITEM_LOCATION_CENTER(item,
BOARDWIDTH/2,
BOARDHEIGHT - 100);
goo_canvas_text_new (boardRootItem,
_("Click on the differences between the two images."),
BOARDWIDTH/2,
BOARDHEIGHT - 100,
-1,
GTK_ANCHOR_CENTER,
"font", gc_skin_font_board_small,
"fill_color_rgba", gc_skin_color_text_button,
"alignment", PANGO_ALIGN_CENTER,
NULL);
}
}
}
static int TestIfClickedOnDiff( int ClickX, int ClickY )
{
int NumDiff = -1;
int ScanPosi,ScanPhoto;
for( ScanPhoto=0; ScanPhoto<2; ScanPhoto++ )
{
for( ScanPosi=0; ScanPosilen; ScanPosi++)
{
int OffsetX = (ScanPhoto==0)?Game.SpaceX:(Game.SpaceX*2+Game.PhotoWidth);
int OffsetY = Game.SpaceY;
GooCanvasBounds * pDiff = &g_array_index (gDiffCoorArray, GooCanvasBounds, ScanPosi);
if ( OffsetX+pDiff->x1 <= ClickX && ClickX<= OffsetX+pDiff->x2
&& OffsetY+pDiff->y1 <= ClickY && ClickY<= OffsetY+pDiff->y2 )
{
NumDiff = ScanPosi;
}
}
}
return NumDiff;
}
static void TestClick( int ClickX, int ClickY )
{
int DiffFound = TestIfClickedOnDiff( ClickX, ClickY );
// a diff found ?
if ( DiffFound!=-1 )
{
GooCanvasBounds * pClickedDiffFound = &g_array_index (gDiffCoorArray, GooCanvasBounds, DiffFound);
// not already found ?
if ( gDiffFoundArray->len>0 )
{
int ScanAlreadyFound;
for( ScanAlreadyFound=0; ScanAlreadyFoundlen; ScanAlreadyFound++)
{
GooCanvasBounds * pScanDiffFound = &g_array_index (gDiffFoundArray, GooCanvasBounds, ScanAlreadyFound);
if ( pScanDiffFound->x1==pClickedDiffFound->x1 && pScanDiffFound->y1==pClickedDiffFound->y1
&& pScanDiffFound->x2==pClickedDiffFound->x2 && pScanDiffFound->y2==pClickedDiffFound->y2 )
{
DiffFound = -1;
}
}
}
if ( DiffFound!=-1 )
{
g_array_append_val( gDiffFoundArray, *pClickedDiffFound );
// draw the found difference on the photos
DrawCircle( Game.SpaceX+pClickedDiffFound->x1, Game.SpaceY+pClickedDiffFound->y1, Game.SpaceX+pClickedDiffFound->x2, Game.SpaceY+pClickedDiffFound->y2, "yellow" );
DrawCircle( (2*Game.SpaceX+Game.PhotoWidth)+pClickedDiffFound->x1, Game.SpaceY+pClickedDiffFound->y1, (2*Game.SpaceX+Game.PhotoWidth)+pClickedDiffFound->x2, Game.SpaceY+pClickedDiffFound->y2, "yellow" );
gc_score_set(gDiffFoundArray->len);
// end ???
if ( gDiffFoundArray->len==gDiffCoorArray->len )
{
gc_bonus_display( GC_BOARD_WIN, GC_BONUS_SMILEY );
LoadNextLevelAfterEndOfBonusDisplay = TRUE;
}
}
}
}
static gint
MouseClick(GooCanvasItem *item, GooCanvasItem *target,
GdkEvent *event, gpointer data)
{
double x, y;
if (!gcomprisBoard || board_paused)
return FALSE;
x = event->button.x;
y = event->button.y;
switch (event->type)
{
case GDK_BUTTON_PRESS:
goo_canvas_convert_from_item_space(goo_canvas_item_get_canvas(item),
item, &x, &y);
TestClick( (int)x, (int)y );
break;
default:
break;
}
return FALSE;
}