1 #include <assert.h>
2 #include <stdlib.h>
3 #include <time.h>
4 #include "merge.h"
5 #include "engine.h"
6 #include "highscore.h"
7 
8 /* Utilize block counter to improve some of the functions so they can run
9  * quicker */
10 
11 /* This function will move all blocks in the given game the given direction.
12  * The callback function is called after each single move. It can be used to
13  * animate the movement of the board. */
gravitate(struct gfx_state * s,struct gamestate * g,int d,void (* callback)(struct gfx_state * s,struct gamestate * g))14 static void gravitate(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g))
15 {
16 
17 #define swap_if_space(xoff, yoff)\
18     do {\
19         if (g->grid[x][y] == 0 && g->grid[x+xoff][y+yoff] != 0) {\
20             g->grid[x][y] = g->grid[x+xoff][y+yoff];\
21             g->grid[x+xoff][y+yoff] = 0;\
22             done = 0;\
23             g->moved = 1;\
24         }\
25     } while (0)
26 
27     int x, y;
28     int done = 0;
29 
30     if (d == dir_left) {
31         while (!done) {
32             done = 1;
33             for (x = 0; x < g->opts->grid_width - 1; ++x) {
34                 for (y = 0; y < g->opts->grid_height; ++y) {
35                     swap_if_space(1, 0);
36                 }
37             }
38             if (callback)
39                 callback(s, g);
40         }
41     }
42     else if (d == dir_right) {
43         while (!done) {
44             done = 1;
45             for (x = g->opts->grid_width - 1; x > 0; --x) {
46                 for (y = 0; y < g->opts->grid_height; ++y) {
47                     swap_if_space(-1, 0);
48                 }
49             }
50             if (callback)
51                 callback(s, g);
52         }
53     }
54     else if (d == dir_down) {
55         while (!done) {
56             done = 1;
57             for (y = g->opts->grid_height - 1; y > 0; --y) {
58                 for (x = 0; x < g->opts->grid_width; ++x) {
59                     swap_if_space(0, -1);
60                 }
61             }
62             if (callback)
63                 callback(s, g);
64         }
65     }
66     else if (d == dir_up) {
67         while (!done) {
68             done = 1;
69             for (y = 0; y < g->opts->grid_height - 1; ++y) {
70                 for (x = 0; x < g->opts->grid_width; ++x) {
71                     swap_if_space(0, 1);
72                 }
73             }
74             if (callback)
75                 callback(s, g);
76         }
77     }
78     else {
79         fatal("Invalid direction passed to gravitate()");
80         /* Not reached */
81     }
82 
83 #undef swap_if_space
84 }
85 
86 /* The merge function will combine adjacent blocks with the same value for
87  * the given direction. Note, left and right merges will merge in a different
88  * order, so they are not identical in all cases.
89  *
90  * Consider 2 2 2 0
91  *
92  * Right merging: 4 0 2 0
93  * Left merging:  2 0 4 0
94  */
merge(struct gfx_state * s,struct gamestate * g,int d,void (* callback)(struct gfx_state * s,struct gamestate * g))95 static void merge(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state *s, struct gamestate *g))
96 {
97 
98 #define merge_if_equal(xoff, yoff)\
99     do {\
100         if (g->grid[x][y] && (merge_possible(g->grid[x][y], g->grid[x+xoff][y+yoff]))) {\
101             g->grid[x][y] = merge_result(g->grid[x][y], g->grid[x+xoff][y+yoff]);\
102             g->grid[x+xoff][y+yoff] = 0;\
103             g->blocks_in_play -= 1;\
104             g->score_last += merge_value(g->grid[x][y]);\
105             g->score += merge_value(g->grid[x][y]);\
106             g->moved = 1;\
107         }\
108     } while (0)
109 
110     int x, y;
111     g->score_last = 0;
112 
113     if (d == dir_left) {
114         for (x = 0; x < g->opts->grid_width - 1; ++x) {
115             for (y = 0; y < g->opts->grid_height; ++y) {
116                 merge_if_equal(1, 0);
117             }
118         }
119     }
120     else if (d == dir_right) {
121         for (x = g->opts->grid_width - 1; x > 0; --x) {
122             for (y = 0; y < g->opts->grid_height; ++y) {
123                 merge_if_equal(-1, 0);
124             }
125         }
126     }
127     else if (d == dir_down) {
128         for (y = g->opts->grid_height - 1; y > 0; --y) {
129             for (x = 0; x < g->opts->grid_width; ++x) {
130                 merge_if_equal(0, -1);
131             }
132         }
133     }
134     else if (d == dir_up) {
135         for (y = 0; y < g->opts->grid_height - 1; ++y) {
136             for (x = 0; x < g->opts->grid_width; ++x) {
137                 merge_if_equal(0, 1);
138             }
139         }
140     }
141     else {
142         fatal("Invalid direction passed to merge()");
143         /* Not reached */
144     }
145 
146     if (callback)
147         callback(s, g);
148 
149 #undef merge_if_equal
150 }
151 
152 /* Scan the current board and determine if an end outcome has been reached.
153  * -1 indicates a lose condition, 1 indicates a win condition, 0 indicates
154  *  end has not yet been reached. */
gamestate_end_condition(struct gamestate * g)155 int gamestate_end_condition(struct gamestate *g)
156 {
157     int ret = -1;
158     //size_t blocks_counted = 0;
159     int x, y;
160 
161     for (x = 0; x < g->opts->grid_width; ++x) {
162         for (y = 0; y < g->opts->grid_height; ++y) {
163             if (g->grid[x][y] == merge_goal())
164                 return 1;
165             if (!g->grid[x][y] || ((x + 1 < g->opts->grid_width) &&
166                         merge_possible(g->grid[x][y], g->grid[x+1][y]))
167                     || ((y + 1 < g->opts->grid_height) &&
168                         merge_possible(g->grid[x][y], g->grid[x][y+1])))
169                 ret = 0;
170         }
171     }
172 
173     return ret;
174 }
175 
176 /* Place a random block into the current grid */
gamestate_new_block(struct gamestate * g)177 void gamestate_new_block(struct gamestate *g)
178 {
179     /* Exit early if there are no spaces to place a block */
180     if (g->blocks_in_play >= g->gridsize) return;
181 
182     int block_number = rand() % (g->gridsize - g->blocks_in_play);
183 
184     int x, y, p = 0;
185     for (y = 0; y < g->opts->grid_height; ++y) {
186         for (x = 0; x < g->opts->grid_width; ++x) {
187             if (!g->grid[x][y]) {
188                 if (p == block_number) {
189                     g->grid[x][y] = rand() & 3 ? 1 : 2;
190                     g->blocks_in_play += 1;
191                     return;
192                 }
193                 else {
194                     ++p;
195                 }
196             }
197         }
198     }
199 
200     /* This should never be reached; but just in case */
201     assert(0);
202 }
203 
204 /* This returns the number of digits in the base10 rep of n. The ceiling is
205  * taken so this will be one greater than required */
digits_ceiling(unsigned int n)206 static int digits_ceiling(unsigned int n)
207 {
208     int l = 0;
209     while (n) n /= 10, ++l;
210     return l + 1;
211 }
212 
213 /* Return NULL if we couldn't allocate space for the gamestate. initializating the
214  * gamestate will parse the options internally, so any caller should pass argc and argv
215  * through this function */
gamestate_init(int argc,char ** argv)216 struct gamestate* gamestate_init(int argc, char **argv)
217 {
218     struct gameoptions *opt = gameoptions_default();
219     if (argc != 0) parse_options(opt, argc, argv);
220 
221     if (!opt) return NULL;
222 
223     srand(time(NULL));
224 
225     struct gamestate *g = malloc(sizeof(struct gamestate));
226     if (!g) goto gamestate_alloc_fail;
227     g->gridsize = opt->grid_width * opt->grid_height;
228 
229     g->grid_data_ptr = calloc(g->gridsize, sizeof(int));
230     if (!g->grid_data_ptr) goto grid_data_alloc_fail;
231 
232     g->grid = malloc(opt->grid_height * sizeof(int*));
233     if (!g->grid) goto grid_alloc_fail;
234 
235     /* Switch to two allocation version */
236     int i;
237     int **iterator = g->grid;
238     for (i = 0; i < g->gridsize; i += opt->grid_width)
239         *iterator++ = &g->grid_data_ptr[i];
240 
241     g->moved = 0;
242     g->score = 0;
243     g->score_high = 0;
244     g->score_last = 0;
245     g->print_width = digits_ceiling(merge_value(merge_goal()));
246     g->blocks_in_play = 0;
247     g->opts = opt;
248 
249     /* Clamp spawn rate to maximum to avoid possible excessive calculation
250      * int generation of blocks */
251     if (g->opts->spawn_rate > g->gridsize)
252         g->opts->spawn_rate = g->gridsize;
253 
254     highscore_load(g);
255 
256     /* Initial 3 random blocks */
257     gamestate_new_block(g);
258     gamestate_new_block(g);
259     gamestate_new_block(g);
260     return g;
261 
262 grid_alloc_fail:
263     free(g->grid_data_ptr);
264 grid_data_alloc_fail:
265     free(g);
266 gamestate_alloc_fail:
267     return NULL;
268 }
269 
270 /* A tick is a gravitate, merge then gravitate all in the same direction.
271  * the moved variable is set to 0 initially and if the gravitate of merge
272  * functions modify it, we can determine which action to take. */
gamestate_tick(struct gfx_state * s,struct gamestate * g,int d,void (* callback)(struct gfx_state *,struct gamestate *))273 int gamestate_tick(struct gfx_state *s, struct gamestate *g, int d, void (*callback)(struct gfx_state*, struct gamestate*))
274 {
275     /* Reset move. Altered by gravitate and merge if we do move */
276     g->moved = 0;
277     gravitate(s, g, d, callback);
278     merge(s, g, d, callback);
279     gravitate(s, g, d, callback);
280     return g->moved;
281 }
282 
283 /* Free all data associated with the gamestate */
gamestate_clear(struct gamestate * g)284 void gamestate_clear(struct gamestate *g)
285 {
286     highscore_save(g);
287     gameoptions_destroy(g->opts);
288     free(g->grid_data_ptr);   /* Free grid data */
289     free(g->grid);      /* Free pointers to data slots */
290     free(g);
291 }
292