]> git.sesse.net Git - vlc/blob - modules/video_filter/puzzle.c
f4de976c9cb02675a476fedc3ea55f361cff8346
[vlc] / modules / video_filter / puzzle.c
1 /*****************************************************************************
2  * puzzle.c : Puzzle game
3  *****************************************************************************
4  * Copyright (C) 2005-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31 #include <math.h>
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_filter.h>
36
37 #include "filter_picture.h"
38
39 /*****************************************************************************
40  * Module descriptor
41  *****************************************************************************/
42 #define ROWS_TEXT N_("Number of puzzle rows")
43 #define ROWS_LONGTEXT N_("Number of puzzle rows")
44 #define COLS_TEXT N_("Number of puzzle columns")
45 #define COLS_LONGTEXT N_("Number of puzzle columns")
46 #define BLACKSLOT_TEXT N_("Make one tile a black slot")
47 #define BLACKSLOT_LONGTEXT N_("Make one slot black. Other tiles can only be swapped with the black slot.")
48
49 #define CFG_PREFIX "puzzle-"
50
51 static int  Open ( vlc_object_t * );
52 static void Close( vlc_object_t * );
53
54 vlc_module_begin()
55     set_description( N_("Puzzle interactive game video filter") )
56     set_shortname( N_( "Puzzle" ))
57     set_capability( "video filter2", 0 )
58     set_category( CAT_VIDEO )
59     set_subcategory( SUBCAT_VIDEO_VFILTER )
60
61     add_integer_with_range( CFG_PREFIX "rows", 4, 1, 128, NULL,
62                             ROWS_TEXT, ROWS_LONGTEXT, false )
63     add_integer_with_range( CFG_PREFIX "cols", 4, 1, 128, NULL,
64                             COLS_TEXT, COLS_LONGTEXT, false )
65     add_bool( CFG_PREFIX "black-slot", 0, NULL,
66               BLACKSLOT_TEXT, BLACKSLOT_LONGTEXT, false )
67
68     set_callbacks( Open, Close )
69 vlc_module_end()
70
71
72 /*****************************************************************************
73  * Local prototypes
74  *****************************************************************************/
75 static const char *const ppsz_filter_options[] = {
76     "rows", "cols", "black-slot", NULL
77 };
78
79 static picture_t *Filter( filter_t *, picture_t * );
80 static int Mouse( filter_t *, vlc_mouse_t *, const vlc_mouse_t *, const vlc_mouse_t * );
81
82 static bool IsFinished( filter_sys_t * );
83 static void Shuffle( filter_sys_t * );
84 static int PuzzleCallback( vlc_object_t *, char const *,
85                            vlc_value_t, vlc_value_t, void * );
86
87 struct filter_sys_t
88 {
89     /* */
90     int i_cols;
91     int i_rows;
92     bool b_blackslot;
93     int *pi_order;
94     int i_selected;
95     bool b_finished;
96
97     /* */
98     vlc_mutex_t lock;
99     bool b_change;
100     struct
101     {
102         int i_cols;
103         int i_rows;
104         bool b_blackslot;
105     } change;
106 };
107
108 #define SHUFFLE_WIDTH 81
109 #define SHUFFLE_HEIGHT 13
110 static const char *shuffle_button[] =
111 {
112 ".................................................................................",
113 "..............  ............................   ........   ......  ...............",
114 "..............  ...........................  .........  ........  ...............",
115 "..............  ...........................  .........  ........  ...............",
116 "..     .......  .    .......  ....  ......     ......     ......  ........    ...",
117 ".  .... ......   ...  ......  ....  .......  .........  ........  .......  ..  ..",
118 ".  ...........  ....  ......  ....  .......  .........  ........  ......  ....  .",
119 ".      .......  ....  ......  ....  .......  .........  ........  ......        .",
120 "..      ......  ....  ......  ....  .......  .........  ........  ......  .......",
121 "......  ......  ....  ......  ....  .......  .........  ........  ......  .......",
122 ". ....  ......  ....  ......  ...   .......  .........  ........  .......  .... .",
123 "..     .......  ....  .......    .  .......  .........  ........  ........     ..",
124 "................................................................................."
125 };
126
127
128 /**
129  * Open the filter
130  */
131 static int Open( vlc_object_t *p_this )
132 {
133     filter_t *p_filter = (filter_t *)p_this;
134     filter_sys_t *p_sys;
135
136     /* */
137     if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) )
138     {
139         msg_Err( p_filter, "Input and output format does not match" );
140         return VLC_EGENERIC;
141     }
142
143     /* Allocate structure */
144     p_filter->p_sys = p_sys = malloc( sizeof( *p_sys ) );
145     if( !p_sys )
146         return VLC_ENOMEM;
147
148     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
149                        p_filter->p_cfg );
150
151     p_sys->pi_order = NULL;
152
153     vlc_mutex_init( &p_sys->lock );
154     p_sys->change.i_rows =
155         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "rows" );
156     p_sys->change.i_cols =
157         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "cols" );
158     p_sys->change.b_blackslot =
159         var_CreateGetBoolCommand( p_filter, CFG_PREFIX "black-slot" );
160     p_sys->b_change = true;
161
162     var_AddCallback( p_filter, CFG_PREFIX "rows", PuzzleCallback, p_sys );
163     var_AddCallback( p_filter, CFG_PREFIX "cols", PuzzleCallback, p_sys );
164     var_AddCallback( p_filter, CFG_PREFIX "black-slot", PuzzleCallback, p_sys );
165
166     p_filter->pf_video_filter = Filter;
167     p_filter->pf_mouse = Mouse;
168
169     return VLC_SUCCESS;
170 }
171
172 /**
173  * Close the filter
174  */
175 static void Close( vlc_object_t *p_this )
176 {
177     filter_t *p_filter = (filter_t *)p_this;
178     filter_sys_t *p_sys = p_filter->p_sys;
179
180     var_DelCallback( p_filter, CFG_PREFIX "rows", PuzzleCallback, p_sys );
181     var_DelCallback( p_filter, CFG_PREFIX "cols", PuzzleCallback, p_sys );
182     var_DelCallback( p_filter, CFG_PREFIX "black-slot", PuzzleCallback, p_sys );
183
184     vlc_mutex_destroy( &p_sys->lock );
185     free( p_sys->pi_order );
186
187     free( p_sys );
188 }
189
190 /**
191  * Filter a picture
192  */
193 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
194 {
195     filter_sys_t *p_sys = p_filter->p_sys;
196
197     picture_t *p_outpic = filter_NewPicture( p_filter );
198     if( !p_outpic )
199     {
200         picture_Release( p_pic );
201         return NULL;
202     }
203
204     /* */
205     vlc_mutex_lock( &p_sys->lock );
206     if( p_sys->b_change )
207     {
208         p_sys->i_rows      = p_sys->change.i_rows;
209         p_sys->i_cols      = p_sys->change.i_cols;
210         p_sys->b_blackslot = p_sys->change.b_blackslot;
211         p_sys->b_change = false;
212
213         Shuffle( p_sys );
214     }
215     vlc_mutex_unlock( &p_sys->lock );
216
217     /* */
218     const int i_rows = p_sys->i_rows;
219     const int i_cols = p_sys->i_cols;
220
221     /* */
222     for( int i_plane = 0; i_plane < p_outpic->i_planes; i_plane++ )
223     {
224         const plane_t *p_in = &p_pic->p[i_plane];
225         const int i_pitch = p_in->i_pitch;
226         plane_t *p_out = &p_outpic->p[i_plane];
227
228         for( int i = 0; i < i_cols * i_rows; i++ )
229         {
230             int i_col = i % i_cols;
231             int i_row = i / i_cols;
232             int i_ocol = p_sys->pi_order[i] % i_cols;
233             int i_orow = p_sys->pi_order[i] / i_cols;
234             int i_last_row = i_row + 1;
235             i_orow *= p_in->i_lines / i_rows;
236             i_row *= p_in->i_lines / i_rows;
237             i_last_row *= p_in->i_lines / i_rows;
238
239             if( p_sys->b_blackslot && p_sys->b_finished && i == p_sys->i_selected )
240             {
241                 uint8_t color = ( i_plane == Y_PLANE ? 0x0 : 0x80 );
242                 for( ; i_row < i_last_row; i_row++, i_orow++ )
243                 {
244                     memset( p_out->p_pixels + i_row * i_pitch + i_col * i_pitch / i_cols,
245                             color, i_pitch / i_cols );
246                 }
247             }
248             else
249             {
250                 for( ; i_row < i_last_row; i_row++, i_orow++ )
251                 {
252                     memcpy( p_out->p_pixels + i_row * i_pitch + i_col * i_pitch / i_cols,
253                             p_in->p_pixels + i_orow * i_pitch + i_ocol * i_pitch / i_cols,
254                             i_pitch / i_cols );
255                 }
256             }
257         }
258     }
259
260     if( p_sys->i_selected != -1 && !p_sys->b_blackslot )
261     {
262         const plane_t *p_in = &p_pic->p[Y_PLANE];
263         const int i_pitch = p_in->i_pitch;
264         plane_t *p_out = &p_outpic->p[Y_PLANE];
265
266         int i_col = p_sys->i_selected % i_cols;
267         int i_row = p_sys->i_selected / i_cols;
268         int i_last_row = i_row + 1;
269         i_row *= p_in->i_lines / i_rows;
270         i_last_row *= p_in->i_lines / i_rows;
271         memset( p_out->p_pixels + i_row * i_pitch + i_col * i_pitch / i_cols,
272                 0xff, i_pitch / i_cols );
273         for( ; i_row < i_last_row; i_row++ )
274         {
275             p_out->p_pixels[   i_row * i_pitch
276                              + i_col * i_pitch / i_cols ] = 0xff;
277             p_out->p_pixels[ i_row * i_pitch
278                              + (i_col+1) * i_pitch / i_cols - 1 ] = 0xff;
279         }
280         i_row--;
281         memset( p_out->p_pixels + i_row * i_pitch + i_col * i_pitch / i_cols,
282                 0xff, i_pitch / i_cols );
283     }
284
285     if( p_sys->b_finished )
286     {
287         plane_t *p_out = &p_outpic->p[Y_PLANE];
288         for( int i = 0; i < SHUFFLE_HEIGHT; i++ )
289         {
290             for( int j = 0; j < SHUFFLE_WIDTH; j++ )
291             {
292                 if( shuffle_button[i][j] == '.' )
293                    p_out->p_pixels[ i * p_out->i_pitch + j ] = 0xff;
294             }
295         }
296     }
297
298     return CopyInfoAndRelease( p_outpic, p_pic );
299 }
300
301 static int Mouse( filter_t *p_filter, vlc_mouse_t *p_mouse,
302                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
303 {
304     filter_sys_t *p_sys = p_filter->p_sys;
305     const video_format_t  *p_fmt = &p_filter->fmt_in.video;
306
307     if( p_new->i_x < 0 || p_new->i_x >= (int)p_fmt->i_width ||
308         p_new->i_y < 0 || p_new->i_y >= (int)p_fmt->i_height )
309         return VLC_EGENERIC;
310
311     /* */
312     const bool b_clicked = vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT );
313
314     if( p_sys->b_finished )
315     {
316         if( b_clicked &&
317             p_new->i_x < SHUFFLE_WIDTH && p_new->i_y < SHUFFLE_HEIGHT )
318         {
319             p_sys->b_change = true;
320             return VLC_EGENERIC;
321         }
322         else
323         {
324             /* This is the only case where we can forward the mouse */
325             *p_mouse = *p_new;
326             return VLC_SUCCESS;
327         }
328     }
329     if( !b_clicked )
330         return VLC_EGENERIC;
331
332     /* */
333     const int i_pos_x = p_new->i_x * p_sys->i_cols / p_fmt->i_width;
334     const int i_pos_y = p_new->i_y * p_sys->i_rows / p_fmt->i_height;
335     const int i_pos = i_pos_y * p_sys->i_cols + i_pos_x;
336
337     if( p_sys->i_selected == -1 )
338     {
339         p_sys->i_selected = i_pos;
340     }
341     else if( p_sys->i_selected == i_pos && !p_sys->b_blackslot )
342     {
343         p_sys->i_selected = -1;
344     }
345     else if( ( p_sys->i_selected == i_pos + 1 && p_sys->i_selected%p_sys->i_cols != 0 )
346           || ( p_sys->i_selected == i_pos - 1 && i_pos % p_sys->i_cols != 0 )
347           || p_sys->i_selected == i_pos + p_sys->i_cols
348           || p_sys->i_selected == i_pos - p_sys->i_cols )
349     {
350         int a = p_sys->pi_order[ p_sys->i_selected ];
351         p_sys->pi_order[ p_sys->i_selected ] = p_sys->pi_order[ i_pos ];
352         p_sys->pi_order[ i_pos ] = a;
353
354         if( p_sys->b_blackslot )
355             p_sys->i_selected = i_pos;
356         else
357             p_sys->i_selected = -1;
358
359         p_sys->b_finished = IsFinished( p_sys );
360     }
361     return VLC_EGENERIC;
362 }
363
364 /*****************************************************************************
365  * Misc stuff...
366  *****************************************************************************/
367 static int PuzzleCallback( vlc_object_t *p_this, char const *psz_var,
368                            vlc_value_t oldval, vlc_value_t newval,
369                            void *p_data )
370 {
371     VLC_UNUSED(p_this); VLC_UNUSED(oldval);
372     filter_sys_t *p_sys = (filter_sys_t *)p_data;
373
374     vlc_mutex_lock( &p_sys->lock );
375     if( !strcmp( psz_var, CFG_PREFIX "rows" ) )
376     {
377         p_sys->change.i_rows = __MAX( 1, newval.i_int );
378     }
379     else if( !strcmp( psz_var, CFG_PREFIX "cols" ) )
380     {
381         p_sys->change.i_cols = __MAX( 1, newval.i_int );
382     }
383     else if( !strcmp( psz_var, CFG_PREFIX "black-slot" ) )
384     {
385         p_sys->change.b_blackslot = newval.b_bool;
386     }
387     p_sys->b_change = true;
388     vlc_mutex_unlock( &p_sys->lock );
389
390     return VLC_SUCCESS;
391 }
392
393 static bool IsFinished( filter_sys_t *p_sys )
394 {
395     for( int i = 0; i < p_sys->i_cols * p_sys->i_rows; i++ )
396     {
397         if( i != p_sys->pi_order[i] )
398             return false;
399     }
400     return true;
401 }
402
403 static bool IsValid( filter_sys_t *p_sys )
404 {
405     const int i_count = p_sys->i_cols * p_sys->i_rows;
406
407     if( p_sys->b_blackslot )
408         return true;
409
410     int d = 0;
411     for( int i = 0; i < i_count; i++ )
412     {
413         if( p_sys->pi_order[i] == i_count - 1 )
414         {
415             d += i / p_sys->i_cols + 1;
416             continue;
417         }
418         for( int j = i+1; j < i_count; j++ )
419         {
420             if( p_sys->pi_order[j] == i_count - 1 )
421                 continue;
422             if( p_sys->pi_order[i] > p_sys->pi_order[j] )
423                 d++;
424         }
425     }
426     return (d%2) == 0;
427 }
428
429 static void Shuffle( filter_sys_t *p_sys )
430 {
431     const int i_count = p_sys->i_cols * p_sys->i_rows;
432
433     free( p_sys->pi_order );
434
435     p_sys->pi_order = calloc( i_count, sizeof(*p_sys->pi_order) );
436     do
437     {
438         for( int i = 0; i < i_count; i++ )
439             p_sys->pi_order[i] = -1;
440
441         for( int c = 0; c < i_count; )
442         {
443             int i = rand() % i_count;
444             if( p_sys->pi_order[i] == -1 )
445                 p_sys->pi_order[i] = c++;
446         }
447         p_sys->b_finished = IsFinished( p_sys );
448
449     } while( p_sys->b_finished || IsValid( p_sys ) );
450
451     if( p_sys->b_blackslot )
452     {
453         for( int i = 0; i < i_count; i++ )
454         {
455             if( p_sys->pi_order[i] == i_count - 1 )
456             {
457                 p_sys->i_selected = i;
458                 break;
459             }
460         }
461     }
462     else
463     {
464         p_sys->i_selected = -1;
465     }
466 }
467