Zmeika
простая аркадная игра
 Указатель Классы Пространства имен Файлы Функции Переменные Определения типов Перечисления Страницы
Game.hpp
См. документацию.
1 /*
2  * This file is part of AV Orchid.
3  * Copyright (c) 2013, Dmitri R. Kuvshinov <evetro.here@gmail.com>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for
6  * any purpose with or without fee is hereby granted, provided that the
7  * above copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
22 #pragma once
23 #include <algorithm>
24 #include <vector>
25 #include <utility>
26 #include <limits>
27 
28 #include "ViewModelBase.hpp"
29 
30 
32 
36 class Field
37 {
38 public:
40 
44  using Position = std::pair<Index, Index>;
45 
47 
53  Field(Index horz, Index vert)
54  : xsize_(horz), ysize_(vert), cells(horz * vert, Cell_Empty)
55  {}
56 
58  Index xsize() const
59  {
60  return xsize_;
61  }
62 
64  Index ysize() const
65  {
66  return ysize_;
67  }
68 
70  Cell operator()(Index x, Index y) const
71  {
72  return cells.at(x + xsize_ * y);
73  }
74 
77  {
78  return cells.at(x + xsize_ * y);
79  }
80 
82  Cell operator[](const Position &p) const
83  {
84  return (*this)(p.first, p.second);
85  }
86 
89  {
90  return (*this)(p.first, p.second);
91  }
92 
93 
95 
100  template <class Visitor>
101  void scan(Visitor visitor) const
102  {
103  visitor (0, 0, cells[0]);
104  for (Index x = 0, i = 1, sz = cells.size(); i < sz; ++i)
105  {
106  if (++x == xsize_)
107  {
108  visitor(1 - xsize_, 1, cells[i]);
109  x = 0;
110  }
111  else
112  {
113  visitor(1, 0, cells[i]);
114  }
115  }
116  }
117 
118 private:
119  Index xsize_, ysize_; // размеры поля
120  std::vector<Cell> cells; // ячейки поля
121 };
122 
123 
125 class Snake
126 {
127 public:
130 
132 
136  explicit Snake(Field &field, Index snake_len = 5)
137  : field(field)
138  {
139  createSnake(snake_len);
140  }
141 
143 
146  void reset(Index snake_len = 0)
147  {
148  for (const auto &p : positions)
149  field[p] = Cell_Empty;
150  createSnake(snake_len);
151  }
152 
154  void up()
155  {
156  vx = 0;
157  vy = +1;
158  check_reverse();
159  }
160 
162  void down()
163  {
164  vx = 0;
165  vy = -1;
166  check_reverse();
167  }
168 
170  void left()
171  {
172  vx = -1;
173  vy = 0;
174  check_reverse();
175  }
176 
178  void right()
179  {
180  vx = +1;
181  vy = 0;
182  check_reverse();
183  }
184 
186 
190  {
191  const Position new_head = moved(positions.front());
192  if (field.xsize() <= new_head.first || field.ysize() <= new_head.second)
193  return Cell_Snake;
194  return warpHead(new_head);
195  }
196 
198 
201  Cell warpHead(const Position &new_head)
202  {
203  switch (const Cell next_cell = field[new_head])
204  {
205  case Cell_Snake:
206  return Cell_Snake;
207 
208  case Cell_Empty:
209  field[positions.back()] = Cell_Empty;
210  positions.pop_back ();
211  // fall-through
212  default:
213  field[new_head] = Cell_Snake;
214  positions.insert (positions.begin(), new_head);
215  return next_cell;
216  }
217  }
218 
220  Index length() const { return positions.size(); }
221 
222 private:
223 
224  using Positions = std::vector<Position>;
226  Positions positions;
227  Index vx, vy; // скорость по x, по y
228  Field &field; // привязка к полю
229 
231  void createSnake(Index snake_len)
232  {
233  vx = 0;
234  vy = 1;
235  positions.resize(snake_len);
236  for (Index i = 0; i < snake_len; ++i)
237  {
238  const Position block(snake_len - i - 1, 0);
239  positions[i] = block;
240  field[block] = Cell_Snake;
241  }
242  }
243 
245  Position moved(const Position &p) const
246  {
247  return Position(p.first + vx, p.second + vy);
248  }
249 
252  {
253  if (1 < positions.size() && moved(positions.front()) == positions[1])
254  {
255  std::reverse(positions.begin(), positions.end());
256  // даже после обращения порядка, "голова" может двигаться в "шею"
257  if (moved(positions.front()) == positions[1])
258  vx = positions.front().first - positions[1].first ,
259  vy = positions.front().second - positions[1].second;
260  }
261  }
262 };
263 
264 
266 struct Level
267 {
269  max_length;
270  double speed;
272  penalty;
273 
274  Level()
275  : start_length(4),
276  max_length(10),
277  speed(4.0),
278  max_movement(1200),
279  penalty(300)
280  {}
281 };
282 
283 
285 
290 {
291  // этот класс введён потому, что мне претит на каждом кадре преобразовывать
292  // число в текст, пусть это и не окажет сколько-нибудь заметного влияния на скорость
293 public:
294  virtual ~NumberPresenter() {} // actually not needed here
295  /*implicit*/ NumberPresenter(Score n = 0);
296 
298  operator Score() const { return number;}
300  const std::string& str() const { return repr; }
301 
302  NumberPresenter& operator+=(Score inc);
303  NumberPresenter& operator++() { return (*this) += 1; }
304 
305 protected:
307  virtual void update(Score n);
308 
309 private:
310  Score number;
311  std::string repr;
312 };
313 
314 
316 
321 {
322 public:
324  : NumberPresenter(n), max_val(n) {}
325 
326  NumberPresenterDecrementable& operator-=(Score inc);
327  NumberPresenterDecrementable& operator--() { return (*this) -= 1; }
329  Score getMax() const { return max_val; }
330 
331 protected:
332  void update(Score n) override;
333 
334 private:
335  Score max_val;
336 };
337 
338 
341 {
342 public:
343  ZmeikaGame();
344 
346  double last_time;
347 
350 
353 
356 
358  void up() { snake.up(); }
360  void down() { snake.down(); }
362  void left() { snake.left(); }
364  void right() { snake.right(); }
365 
367 
374  bool step(double t);
375 
377  void start()
378  {
379  readLevels();
380  newRabbit(Cell_Rabbit);
381  selectLevel(1);
382  score = 0;
383  }
384 
386 
389  bool warpSnake(const Field::Position &new_head)
390  {
391  return movedInto(snake.warpHead(new_head));
392  }
393 
394 private:
395 
397  void readLevels();
398 
400  void newRabbit(Cell rabbitType);
402  void selectLevel(Index next_level);
403 
405 
408  bool movedInto(const Cell);
409 
413  Score move_score[Cell_Types];
415  double time_step;
417  std::vector<Level> levels;
422  // Осторожно: конструктор принимает field, поэтому snake дб расположена
423  // в классе ниже, чем field, чтобы конструктор field отработал до конструктора snake.
424 };