]> git.sesse.net Git - vlc/blob - modules/video_filter/puzzle.c
Use var_InheritString for --decklink-video-connection.
[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 #include <vlc_rand.h>
37
38 #include "filter_picture.h"
39
40 /*****************************************************************************
41  * Module descriptor
42  *****************************************************************************/
43 #define ROWS_TEXT N_("Number of puzzle rows")
44 #define ROWS_LONGTEXT N_("Number of puzzle rows")
45 #define COLS_TEXT N_("Number of puzzle columns")
46 #define COLS_LONGTEXT N_("Number of puzzle columns")
47 #define BLACKSLOT_TEXT N_("Make one tile a black slot")
48 #define BLACKSLOT_LONGTEXT N_("Make one slot black. Other tiles can only be swapped with the black slot.")
49
50 #define CFG_PREFIX "puzzle-"
51
52 static int  Open ( vlc_object_t * );
53 static void Close( vlc_object_t * );
54
55 vlc_module_begin()
56     set_description( N_("Puzzle interactive game video filter") )
57     set_shortname( N_( "Puzzle" ))
58     set_capability( "video filter2", 0 )
59     set_category( CAT_VIDEO )
60     set_subcategory( SUBCAT_VIDEO_VFILTER )
61
62     add_integer_with_range( CFG_PREFIX "rows", 4, 2, 16, NULL,
63                             ROWS_TEXT, ROWS_LONGTEXT, false )
64     add_integer_with_range( CFG_PREFIX "cols", 4, 2, 16, NULL,
65                             COLS_TEXT, COLS_LONGTEXT, false )
66     add_bool( CFG_PREFIX "black-slot", false, NULL,
67               BLACKSLOT_TEXT, BLACKSLOT_LONGTEXT, false )
68
69     set_callbacks( Open, Close )
70 vlc_module_end()
71
72
73 /*****************************************************************************
74  * Local prototypes
75  *****************************************************************************/
76 static const char *const ppsz_filter_options[] = {
77     "rows", "cols", "black-slot", NULL
78 };
79
80 static picture_t *Filter( filter_t *, picture_t * );
81 static int Mouse( filter_t *, vlc_mouse_t *, const vlc_mouse_t *, const vlc_mouse_t * );
82
83 static bool IsFinished( filter_sys_t * );
84 static void Shuffle( filter_sys_t * );
85 static int PuzzleCallback( vlc_object_t *, char const *,
86                            vlc_value_t, vlc_value_t, void * );
87
88 struct filter_sys_t
89 {
90     /* */
91     int i_cols;
92     int i_rows;
93     bool b_blackslot;
94     int *pi_order;
95     int i_selected;
96     bool b_finished;
97
98     /* */
99     vlc_mutex_t lock;
100     bool b_change;
101     struct
102     {
103         int i_cols;
104         int i_rows;
105         bool b_blackslot;
106     } change;
107 };
108
109 #define SHUFFLE_WIDTH 81
110 #define SHUFFLE_HEIGHT 13
111 static const char *shuffle_button[] =
112 {
113 ".................................................................................",
114 "..............  ............................   ........   ......  ...............",
115 "..............  ...........................  .........  ........  ...............",
116 "..............  ...........................  .........  ........  ...............",
117 "..     .......  .    .......  ....  ......     ......     ......  ........    ...",
118 ".  .... ......   ...  ......  ....  .......  .........  ........  .......  ..  ..",
119 ".  ...........  ....  ......  ....  .......  .........  ........  ......  ....  .",
120 ".      .......  ....  ......  ....  .......  .........  ........  ......        .",
121 "..      ......  ....  ......  ....  .......  .........  ........  ......  .......",
122 "......  ......  ....  ......  ....  .......  .........  ........  ......  .......",
123 ". ....  ......  ....  ......  ...   .......  .........  ........  .......  .... .",
124 "..     .......  ....  .......    .  .......  .........  ........  ........     ..",
125 "................................................................................."
126 };
127
128
129 /**
130  * Open the filter
131  */
132 static int Open( vlc_object_t *p_this )
133 {
134     filter_t *p_filter = (filter_t *)p_this;
135     filter_sys_t *p_sys;
136
137     /* */
138     if( !es_format_IsSimilar( &p_filter->fmt_in, &p_filter->fmt_out ) )
139     {
140         msg_Err( p_filter, "Input and output format does not match" );
141         return VLC_EGENERIC;
142     }
143
144     /* Allocate structure */
145     p_filter->p_sys = p_sys = malloc( sizeof( *p_sys ) );
146     if( !p_sys )
147         return VLC_ENOMEM;
148
149     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
150                        p_filter->p_cfg );
151
152     p_sys->pi_order = NULL;
153
154     vlc_mutex_init( &p_sys->lock );
155     p_sys->change.i_rows =
156         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "rows" );
157     p_sys->change.i_cols =
158         var_CreateGetIntegerCommand( p_filter, CFG_PREFIX "cols" );
159     p_sys->change.b_blackslot =
160         var_CreateGetBoolCommand( p_filter, CFG_PREFIX "black-slot" );
161     p_sys->b_change = true;
162
163     var_AddCallback( p_filter, CFG_PREFIX "rows", PuzzleCallback, p_sys );
164     var_AddCallback( p_filter, CFG_PREFIX "cols", PuzzleCallback, p_sys );
165     var_AddCallback( p_filter, CFG_PREFIX "black-slot", PuzzleCallback, p_sys );
166
167     p_filter->pf_video_filter = Filter;
168     p_filter->pf_video_mouse = Mouse;
169
170     return VLC_SUCCESS;
171 }
172
173 /**
174  * Close the filter
175  */
176 static void Close( vlc_object_t *p_this )
177 {
178     filter_t *p_filter = (filter_t *)p_this;
179     filter_sys_t *p_sys = p_filter->p_sys;
180
181     var_DelCallback( p_filter, CFG_PREFIX "rows", PuzzleCallback, p_sys );
182     var_DelCallback( p_filter, CFG_PREFIX "cols", PuzzleCallback, p_sys );
183     var_DelCallback( p_filter, CFG_PREFIX "black-slot", PuzzleCallback, p_sys );
184
185     vlc_mutex_destroy( &p_sys->lock );
186     free( p_sys->pi_order );
187
188     free( p_sys );
189 }
190
191 /**
192  * Filter a picture
193  */
194 static picture_t *Filter( filter_t *p_filter, picture_t *p_pic )
195 {
196     filter_sys_t *p_sys = p_filter->p_sys;
197
198     picture_t *p_outpic = filter_NewPicture( p_filter );
199     if( !p_outpic )
200     {
201         picture_Release( p_pic );
202         return NULL;
203     }
204
205     /* */
206     vlc_mutex_lock( &p_sys->lock );
207     if( p_sys->b_change )
208     {
209         p_sys->i_rows      = p_sys->change.i_rows;
210         p_sys->i_cols      = p_sys->change.i_cols;
211         p_sys->b_blackslot = p_sys->change.b_blackslot;
212         p_sys->b_change = false;
213
214         Shuffle( p_sys );
215     }
216     vlc_mutex_unlock( &p_sys->lock );
217
218     /* */
219     const int i_rows = p_sys->i_rows;
220     const int i_cols = p_sys->i_cols;
221
222     /* Draw each piece of the puzzle at the right place */
223     for( int i_plane = 0; i_plane < p_outpic->i_planes; i_plane++ )
224     {
225         const plane_t *p_in = &p_pic->p[i_plane];
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_piece_height = p_out->i_visible_lines / i_rows;
231             int i_piece_width  = p_out->i_visible_pitch / i_cols;
232
233             int i_col = (i % i_cols) * i_piece_width;
234             int i_row = (i / i_cols) * i_piece_height;
235             int i_last_row = i_row + i_piece_height;
236
237             int i_ocol = (p_sys->pi_order[i] % i_cols) * i_piece_width;
238             int i_orow = (p_sys->pi_order[i] / i_cols) * i_piece_height;
239
240             if( p_sys->b_blackslot && !p_sys->b_finished && i == p_sys->i_selected )
241             {
242                 uint8_t color = ( i_plane == Y_PLANE ? 0x0 : 0x80 );
243                 for( int r = i_row; r < i_last_row; r++ )
244                 {
245                     memset( p_out->p_pixels + r * p_out->i_pitch + i_col,
246                             color, i_piece_width );
247                 }
248             }
249             else
250             {
251                 for( int r = i_row, or = i_orow; r < i_last_row; r++, or++ )
252                 {
253                     memcpy( p_out->p_pixels + r * p_out->i_pitch + i_col,
254                             p_in->p_pixels + or * p_in->i_pitch  + i_ocol,
255                             i_piece_width );
256                 }
257             }
258
259             /* Draw the borders of the selected slot */
260             if( i_plane == 0 && !p_sys->b_blackslot && p_sys->i_selected == i )
261             {
262                 memset( p_out->p_pixels + i_row * p_out->i_pitch + i_col,
263                         0xff, i_piece_width );
264                 for( int r = i_row; r < i_last_row; r++ )
265                 {
266                     p_out->p_pixels[r * p_out->i_pitch + i_col + 0             + 0 ] = 0xff;
267                     p_out->p_pixels[r * p_out->i_pitch + i_col + i_piece_width - 1 ] = 0xff;
268                 }
269                 memset( p_out->p_pixels + (i_last_row - 1) * p_out->i_pitch + i_col,
270                         0xff, i_piece_width );
271             }
272         }
273     }
274
275     /* Draw the 'Shuffle' button if the puzzle is finished */
276     if( p_sys->b_finished )
277     {
278         plane_t *p_out = &p_outpic->p[Y_PLANE];
279         for( int i = 0; i < SHUFFLE_HEIGHT; i++ )
280         {
281             for( int j = 0; j < SHUFFLE_WIDTH; j++ )
282             {
283                 if( shuffle_button[i][j] == '.' )
284                    p_out->p_pixels[ i * p_out->i_pitch + j ] = 0xff;
285             }
286         }
287     }
288
289     return CopyInfoAndRelease( p_outpic, p_pic );
290 }
291
292 static int Mouse( filter_t *p_filter, vlc_mouse_t *p_mouse,
293                   const vlc_mouse_t *p_old, const vlc_mouse_t *p_new )
294 {
295     filter_sys_t *p_sys = p_filter->p_sys;
296     const video_format_t  *p_fmt = &p_filter->fmt_in.video;
297
298     /* Only take events inside the puzzle erea */
299     if( p_new->i_x < 0 || p_new->i_x >= (int)p_fmt->i_width ||
300         p_new->i_y < 0 || p_new->i_y >= (int)p_fmt->i_height )
301         return VLC_EGENERIC;
302
303     /* */
304     const bool b_clicked = vlc_mouse_HasPressed( p_old, p_new, MOUSE_BUTTON_LEFT );
305
306     /* If the puzzle is finished, shuffle it if needed */
307     if( p_sys->b_finished )
308     {
309         if( b_clicked &&
310             p_new->i_x < SHUFFLE_WIDTH && p_new->i_y < SHUFFLE_HEIGHT )
311         {
312             p_sys->b_change = true;
313             return VLC_EGENERIC;
314         }
315         else
316         {
317             /* This is the only case where we can forward the mouse */
318             *p_mouse = *p_new;
319             return VLC_SUCCESS;
320         }
321     }
322     if( !b_clicked )
323         return VLC_EGENERIC;
324
325     /* */
326     const int i_pos_x = p_new->i_x * p_sys->i_cols / p_fmt->i_width;
327     const int i_pos_y = p_new->i_y * p_sys->i_rows / p_fmt->i_height;
328     const int i_pos = i_pos_y * p_sys->i_cols + i_pos_x;
329
330     if( p_sys->i_selected == -1 )
331     {
332         p_sys->i_selected = i_pos;
333     }
334     else if( p_sys->i_selected == i_pos && !p_sys->b_blackslot )
335     {
336         p_sys->i_selected = -1;
337     }
338     else if( ( p_sys->i_selected == i_pos + 1 && p_sys->i_selected%p_sys->i_cols != 0 )
339           || ( p_sys->i_selected == i_pos - 1 && i_pos % p_sys->i_cols != 0 )
340           || p_sys->i_selected == i_pos + p_sys->i_cols
341           || p_sys->i_selected == i_pos - p_sys->i_cols )
342     {
343         /* Swap two pieces */
344         int a = p_sys->pi_order[ p_sys->i_selected ];
345         p_sys->pi_order[ p_sys->i_selected ] = p_sys->pi_order[ i_pos ];
346         p_sys->pi_order[ i_pos ] = a;
347
348         p_sys->i_selected = p_sys->b_blackslot ? i_pos : -1;
349         p_sys->b_finished = IsFinished( p_sys );
350     }
351     return VLC_EGENERIC;
352 }
353
354 /*****************************************************************************
355  * Misc stuff...
356  *****************************************************************************/
357 static int PuzzleCallback( vlc_object_t *p_this, char const *psz_var,
358                            vlc_value_t oldval, vlc_value_t newval,
359                            void *p_data )
360 {
361     VLC_UNUSED(p_this); VLC_UNUSED(oldval);
362     filter_sys_t *p_sys = (filter_sys_t *)p_data;
363
364     vlc_mutex_lock( &p_sys->lock );
365     if( !strcmp( psz_var, CFG_PREFIX "rows" ) )
366     {
367         p_sys->change.i_rows = __MAX( 1, newval.i_int );
368     }
369     else if( !strcmp( psz_var, CFG_PREFIX "cols" ) )
370     {
371         p_sys->change.i_cols = __MAX( 1, newval.i_int );
372     }
373     else if( !strcmp( psz_var, CFG_PREFIX "black-slot" ) )
374     {
375         p_sys->change.b_blackslot = newval.b_bool;
376     }
377     p_sys->b_change = true;
378     vlc_mutex_unlock( &p_sys->lock );
379
380     return VLC_SUCCESS;
381 }
382
383 static bool IsFinished( filter_sys_t *p_sys )
384 {
385     for( int i = 0; i < p_sys->i_cols * p_sys->i_rows; i++ )
386     {
387         if( i != p_sys->pi_order[i] )
388             return false;
389     }
390     return true;
391 }
392
393 static bool IsValid( filter_sys_t *p_sys )
394 {
395     const int i_count = p_sys->i_cols * p_sys->i_rows;
396
397     if( !p_sys->b_blackslot )
398         return true;
399
400     int d = 0;
401     for( int i = 0; i < i_count; i++ )
402     {
403         if( p_sys->pi_order[i] == i_count - 1 )
404         {
405             d += i / p_sys->i_cols + 1;
406             continue;
407         }
408         for( int j = i+1; j < i_count; j++ )
409         {
410             if( p_sys->pi_order[j] == i_count - 1 )
411                 continue;
412             if( p_sys->pi_order[i] > p_sys->pi_order[j] )
413                 d++;
414         }
415     }
416     return (d%2) == 0;
417 }
418
419 static void Shuffle( filter_sys_t *p_sys )
420 {
421     const unsigned i_count = p_sys->i_cols * p_sys->i_rows;
422
423     free( p_sys->pi_order );
424
425     p_sys->pi_order = calloc( i_count, sizeof(*p_sys->pi_order) );
426     do
427     {
428         for( unsigned i = 0; i < i_count; i++ )
429             p_sys->pi_order[i] = -1;
430
431         for( unsigned c = 0; c < i_count; )
432         {
433             unsigned i = ((unsigned)vlc_mrand48()) % i_count;
434             if( p_sys->pi_order[i] == -1 )
435                 p_sys->pi_order[i] = c++;
436         }
437         p_sys->b_finished = IsFinished( p_sys );
438
439     } while( p_sys->b_finished || !IsValid( p_sys ) );
440
441     if( p_sys->b_blackslot )
442     {
443         for( unsigned i = 0; i < i_count; i++ )
444         {
445             if( p_sys->pi_order[i] == i_count - 1 )
446             {
447                 p_sys->i_selected = i;
448                 break;
449             }
450         }
451     }
452     else
453     {
454         p_sys->i_selected = -1;
455     }
456 }
457