C ++ 및 FLTK를 사용한 스네이크 게임
기술
C ++ 및 FLTK를 사용하여 스네이크 게임을 작성했습니다. FLTK 사용을 단순화하기 위해 Bjarne Stroustrup이 작성한 내장 라이브러리가 사용되었습니다. Bellow는 내가 작성한 코드의 주요 부분에 위치하고 있으며 전체 프로젝트는 GitHub에서 찾을 수 있습니다.https://github.com/WumpusHunter/Snake-game.
Source.cpp
/*
Snake game
Revision history:
Written by Oleg Kovalevskiy in August 2020
*/
//------------------------------------------------------------------------------------
#include "Game_window.h"
using namespace Graph_lib;
//------------------------------------------------------------------------------------
int main()
try {
// Window with top-left angle at (100, 100), of size 600 * 400, labeled "Snake game"
Snake_window win{ Point{ 100, 100 }, 600, 400, "Snake game" };
return gui_main();
}
catch (const exception& e) {
cerr << "Error message: " << e.what() << '\n';
return 1;
}
catch (...) {
cerr << "Unknown error\n";
return 1;
}
//------------------------------------------------------------------------------------
Game_window.h
// Snake game's window
//------------------------------------------------------------------------------
#pragma once
#include "GraphicsLib/Window.h"
#include "GraphicsLib/GUI.h"
#include "GraphicsLib/Graph.h"
#include "Game_graph.h"
//------------------------------------------------------------------------------
namespace Graph_lib {
//------------------------------------------------------------------------------
// Invariant: w > 0, h > 0
class Snake_window : public Window { // Game window
public:
// Construction
Snake_window(Point xy, int w, int h, const string& lab);
private:
// Callback functions
int handle(int event) override;
static void cb_game_loop(Address pw);
static void cb_pause(Address, Address pw);
static void cb_new_game(Address, Address pw);
static void cb_quit(Address, Address pw);
static void cb_game(Address, Address pw);
static void cb_help(Address, Address pw);
// Action functions
void start();
void game_loop();
bool is_pause();
void pause();
void new_game();
void quit();
void game();
void help();
int current_score();
void put_score();
void show_graphics();
void hide_graphics();
private:
// Graphics
Grid field;
Snake snake;
Rectangle fruit;
// GUI
Menu game_menu;
Button game_button;
Button help_button;
Text_box help_box;
Out_box score_box;
Out_box max_score_box;
};
//------------------------------------------------------------------------------
} // End of Graph_lib namespace
//------------------------------------------------------------------------------
Game_window.cpp
// Snake game's window
//------------------------------------------------------------------------------
#include "Game_window.h"
//------------------------------------------------------------------------------
namespace Graph_lib {
//------------------------------------------------------------------------------
// Min possible size of window
constexpr int min_w = 400; // Window's min width
constexpr int min_h = 300; // Window's min height
// Size of cells
constexpr int cell_w = 50; // Cell's width
constexpr int cell_h = 50; // Cell's height
// Default parameters of snake
constexpr int snake_sz = 3; // Snake's length
// Default location of graphics
Point snake_xy = { 0, 0 }; // Snake's location
Point fruit_xy = { 0, 0 }; // Fruit's location
// Size of widgets
constexpr int widget_h = 25; // Widgets' height
constexpr int out_box_w = 30; // Output boxes' width
constexpr int button_w = 100; // Buttons' width
// Indexes of game menu's buttons
constexpr int new_game_ind = 0; // New game button's index
constexpr int pause_ind = 1; // Pause button's index
constexpr int quit_ind = 2; // Quit button's index
// Constructs window with top-left angle at xy, of size w * h (if
// it's not less than min, which is 400 * 300), labeled with lab
Snake_window::Snake_window(Point xy, int w, int h, const string& lab)
: Window{ xy, w >= min_w ? w - w % cell_w : min_w, h >= min_h ? h - h % cell_h : min_h, lab },
field{ Point{ 0, cell_h }, cell_w, cell_h, x_max() / cell_w, (y_max() - cell_h) / cell_h },
snake{ Point{ snake_sz * cell_w, y_max() / 2 }, cell_w, cell_h, snake_sz },
fruit{ Point{ x_max() - cell_w * 2, y_max() / 2 }, cell_w, cell_h },
game_menu{ Point{ 0, 0 }, button_w, widget_h, Menu::Kind::horizontal, "Game" },
game_button{ Point{ 0, 0 }, button_w, widget_h, "&Game", cb_game },
help_button{ Point{ button_w, 0 }, button_w, widget_h, "&Help", cb_help },
help_box{ Point{ 0, cell_h }, x_max(), y_max() - cell_h, "" },
score_box{ Point{ cell_w * 2, widget_h }, out_box_w, widget_h, "Current score: " },
max_score_box{ Point{ cell_w * 4 + out_box_w, widget_h }, out_box_w, widget_h, "Max score: " }
{
if (w <= 0 || h <= 0) // Error handling
throw invalid_argument("Bad Snake_window: non-positive size");
// Keep default location of graphics
snake_xy = snake.point(0);
fruit_xy = fruit.point(0);
// Attach graphics to window
attach(field);
attach(snake);
attach(fruit);
// Attach widgets to window
game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&New game", cb_new_game });
game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&Pause", cb_pause });
game_menu.attach(new Button{ Point{ 0, 0 }, 0, 0, "&Quit", cb_quit });
attach(game_menu);
attach(game_button);
attach(help_button);
attach(help_box);
attach(score_box);
attach(max_score_box);
// Default value for graphics
show_graphics();
put_on_top(snake);
// Default value for widgets
game_menu.hide();
help_box.put(" SNAKE GAME\n"
" Snake is a video game concept where the player maneuvers a line\n"
"that grows in length, with the line itself being a primary obstacle.\n"
"The concept originated in the 1976 arcade game Blockade.\n"
" GAMEPLAY\n"
" The player controls an object on a bordered plane. As it moves for-\n"
"ward, it leaves a trail behind, resembling a moving snake. The snake\n"
"has a specific length, so there is a moving tail a fixed number of units\n"
"away from the head. The player loses when the snake runs into the\n"
"screen border or itself.\n"
" A sole player attempts to eat items by running into them with the he-\n"
"ad of the snake. Each item eaten makes the snake longer, so con-\n"
"trolling is progressively more difficult.\n"
" CONTROL\n"
" The snake moves forward automatically, everything you need to do\n"
"is to choose the direction of moving. To choose the direction of mov-\n"
"ing use arrow-buttons, that is,\n"
"1) Left-arrow - to move in the left direction;\n"
"2) Up-arrow - to move in the up direction;\n"
"3) Right-arrow - to move in the right direction;\n"
"4) Down-arrow - to move in the down direction.\n"
"Remember: you can't rotate the snake's head to the opposite direc-\n"
"tion, for instance, from the left to the right, or from the up to the\n"
"down.\n"
" ADDITIONAL NOTES\n"
" Good luck on the game, try to eat as much as you can!\n");
help_box.hide();
score_box.put(0);
max_score_box.put(0);
}
// Handles passed to window event, for instance, pressed key
int Snake_window::handle(int event)
{
switch (event) {
case FL_FOCUS: case FL_UNFOCUS: // Focuses are skipped (required by system)
return 1;
case FL_KEYBOARD: { // Keys, pressed using keyboard
switch (Fl::event_key()) {
// Arrow-keys used to change snake's direction
case FL_Left: // Left-arrow
snake.set_direction(Snake::Direction::left);
cout << "Changed direction to the left (" << static_cast<int>(snake.direction()) << ")\n";
return 1;
case FL_Up: // Up-arrow
snake.set_direction(Snake::Direction::up);
cout << "Changed direction to the up (" << static_cast<int>(snake.direction()) << ")\n";
return 1;
case FL_Right: // Right-arrow
snake.set_direction(Snake::Direction::right);
cout << "Changed direction to the right (" << static_cast<int>(snake.direction()) << ")\n";
return 1;
case FL_Down: // Down-arrow
snake.set_direction(Snake::Direction::down);
cout << "Changed direction to the down (" << static_cast<int>(snake.direction()) << ")\n";
return 1;
}
}
}
return Window::handle(event); // Everything else is handled by base window
}
// Callback function for game_loop
void Snake_window::cb_game_loop(Address pw)
{
constexpr double delay = 0.25; // Delay of game's loop
reference_to<Snake_window>(pw).game_loop(); // Call of action function
Fl::repeat_timeout(delay, cb_game_loop, pw); // Execute delay of game's loop
}
// Callback function for pause
void Snake_window::cb_pause(Address, Address pw)
{
reference_to<Snake_window>(pw).pause();
reference_to<Snake_window>(pw).game();
}
// Callback function for new game
void Snake_window::cb_new_game(Address, Address pw)
{
reference_to<Snake_window>(pw).new_game();
reference_to<Snake_window>(pw).game();
}
// Callback function for quit
void Snake_window::cb_quit(Address, Address pw)
{
reference_to<Snake_window>(pw).quit();
reference_to<Snake_window>(pw).game();
}
// Callback function for game
void Snake_window::cb_game(Address, Address pw)
{
reference_to<Snake_window>(pw).game();
}
// Callback function for help
void Snake_window::cb_help(Address, Address pw)
{
reference_to<Snake_window>(pw).help();
}
// Starts game's loop
void Snake_window::start()
{
constexpr double delay = 1.0; // Delay before first timeout
Fl::add_timeout(delay, cb_game_loop, this); // Start game's loop and delay proccess
cout << "Started the game\n";
}
// Starts all proccesses of game's loop
void Snake_window::game_loop()
{
// Snake's bumping (obstacle is snake's body or field's borders)
if (snake.is_body_except_head(snake.body_head())) { // Snake's body as obstacle
cout << "Bumped into the snake's body\n";
// Pause after losed game
return Fl::add_timeout(0.0, [](Address pw) { cb_pause(nullptr, pw); }, this);;
}
if (!is_grid(field, snake.body_head())) { // Grid's border as obstacle
cout << "Bumped into the grid's border\n";
// Pause after losed game
return Fl::add_timeout(0.0, [](Address pw) { cb_pause(nullptr, pw); }, this);
}
// Snake's eating
if (snake.point(0) == fruit.point(0)) {
snake.grow_length();
put_score(); // Update score after eating
cout << "Ate the fruit; the length becomes equal to " << snake.length() << '\n';
// Randomly change location of fruit to everywhere, except snake's body
while (snake.is_body(fruit))
random_move(fruit, field.point(0), field.width() - fruit.width(), field.height() - fruit.height());
}
else snake.move_forward(); // Snake's moving
cout << "Moved to (" << snake.point(0).x << ", " << snake.point(0).y << ")\n";
redraw(); // Redraw window after made changes
}
// Determines either game is paused or not
bool Snake_window::is_pause()
{
return Fl::has_timeout(cb_game_loop, this) ? false : true;
}
// Pauses game if it's playing, or starts if it's already
// paused, that is, pause prevents snake's moves
void Snake_window::pause()
{
if (!is_pause()) {
Fl::remove_timeout(cb_game_loop, this); // Stop timeout
cout << "Paused the game\n";
}
else start(); // Start timeout
}
// Starts new game, that is, returns everything to initial state
void Snake_window::new_game()
{
if (!is_pause()) pause(); // Pause game
snake.shrink_length(current_score()); // Shrink length to default length
// Return graphics to default location
snake.set_direction(Snake::Direction::up);
snake.set_direction(Snake::Direction::right);
for (int i = 0; i < snake_sz; ++i)
snake.move_forward();
snake.move(-snake.point(0).x, -snake.point(0).y); // Top-left angle of window
snake.move(snake_xy.x, snake_xy.y);
fruit.move(-fruit.point(0).x, -fruit.point(0).y); // Top-left angle of window
fruit.move(fruit_xy.x, fruit_xy.y);
cout << "Started the new game; shrank the length to " << snake.length() << '\n';
put_score(); // Update score after shrinking
redraw(); // Redraw window after made changes
}
// Quits game, that is, closes window
void Snake_window::quit()
{
Window::hide(); // Hide window to close it
cout << "Quited the game\n";
}
// Hides game button and shows game menu, if game button is pressed,
// or shows game button and hides game menu, if game menu is pressed
void Snake_window::game()
{
// Hide game button and show game menu
if (game_button.visible()) { // Game button is pressed
game_button.hide();
game_menu.show();
help_button.move(game_menu.selection.size() * game_menu.width - help_button.width, 0);
cout << "Hid the game button and showed the game menu\n";
}
// Hide game menu and show game button
else { // Game menu is pressed
game_menu.hide();
game_button.show();
help_button.move(help_button.width - game_menu.selection.size() * game_menu.width, 0);
cout << "Hid the game menu and showed the game button\n";
}
}
// Shows help box if it's invisible, or hides it if it's visible
void Snake_window::help()
{
// Show help box
if (!help_box.visible()) { // Help box is invisible
if (!is_pause()) pause(); // Pause game
game_menu.selection[pause_ind].deactivate();
hide_graphics();
help_box.show();
cout << "Showed the help box\n";
}
// Hide help box
else { // Help box is visible
game_menu.selection[pause_ind].activate();
help_box.hide();
show_graphics();
cout << "Hid the help box\n";
}
}
// Determines current score
int Snake_window::current_score()
{
return snake.length() - snake_sz;
}
// Writes current score and max score into score boxes, if required
void Snake_window::put_score()
{
int score = current_score();
score_box.put(score); // Write current score
if (score > max_score_box.get_int()) { // New record
max_score_box.put(score); // Write max score
cout << "Updated the max score to " << score << '\n';
}
cout << "Updated the current score to " << score << '\n';
}
// Shows game's graphics, that is, makes field, snake, and fruit visible
void Snake_window::show_graphics()
{
// Modify color parameters of graphics
field.set_color(Color::black);
field.set_fill_color(Color::dark_green);
snake.set_color(Color::black);
snake.set_fill_color(Color::dark_yellow);
snake.head_set_fill_color(Color::yellow);
fruit.set_color(Color::black);
fruit.set_fill_color(Color::red);
cout << "Showed the graphics\n";
}
// Hides game's graphics, that is, makes field, snake, and fruit invisible
void Snake_window::hide_graphics()
{
// Modify color parameters of graphics
field.set_color(Color::invisible);
field.set_fill_color(Color::invisible);
snake.set_color(Color::invisible);
snake.set_fill_color(Color::invisible);
snake.head_set_fill_color(Color::invisible);
fruit.set_color(Color::invisible);
fruit.set_fill_color(Color::invisible);
cout << "Hid the graphics\n";
}
//------------------------------------------------------------------------------
} // End of Graph_lib namespace
//------------------------------------------------------------------------------
Game_graph.h
// Snake game's graphics
//------------------------------------------------------------------------------
#pragma once
#include "GraphicsLib/Graph.h"
//------------------------------------------------------------------------------
namespace Graph_lib {
//------------------------------------------------------------------------------
// Invariant: cell_w > 0, cell_h > 0, sz > 0
class Snake : public Shape {
public:
enum class Direction { // Possible directions of head
left, up, right, down
};
// Construction
Snake(Point xy, int cell_w, int cell_h, int sz);
// Drawing
void draw_lines() const override;
void move(int dx, int dy) override;
void move_forward();
void grow_length();
void shrink_length(int num);
// Modification of parameters
void set_color(Color c);
void set_fill_color(Color c);
void set_style(Line_style ls);
void set_direction(Direction d);
void head_set_fill_color(Color c);
// Access to parameters
const Rectangle& body_head() const;
Direction direction() const { return head; }
int length() const { return body.size(); }
bool is_body(const Rectangle& cell) const;
bool is_body_except_head(const Rectangle& cell) const;
private:
Vector_ref<Rectangle> body;
Direction head; // Direction of head
};
//------------------------------------------------------------------------------
// Helper function
void random_move(Rectangle& rect, Point xy, int w, int h);
//------------------------------------------------------------------------------
} // End of Graph_lib namespace
//------------------------------------------------------------------------------
Game_graph.cpp
// Snake game's graphics
//------------------------------------------------------------------------------
#include "Game_graph.h"
#include "RandomNumber/Generator.h"
//------------------------------------------------------------------------------
namespace Graph_lib {
//------------------------------------------------------------------------------
// Indexes of snake's body
constexpr int head_ind = 0;
// Constructs snake with top left-angle of its head at xy, of sz
// cells, and with size of each cell equal to cell_w * cell_h
Snake::Snake(Point xy, int cell_w, int cell_h, int sz)
: body{}, head{ Direction::right }
{
if (sz <= 0) // Error handling
throw invalid_argument("Bad Snake: non-positive length");
// Fill of body
for (int i = 0; i < sz; ++i) // Horizontal line
body.push_back(new Rectangle{ Point{ xy.x - i * cell_w, xy.y }, cell_w, cell_h });
add(xy); // Top-left angle of snake's head
}
// Draws snake and fills it with color if required
void Snake::draw_lines() const
{
// Draw each cell of body
for (int i = 0; i < body.size(); ++i)
body[i].draw();
}
// Moves snake by dx at x-coordinate and dy at y-coordinate
void Snake::move(int dx, int dy)
{
Shape::move(dx, dy);
// Move each cell of body
for (int i = 0; i < body.size(); ++i)
body[i].move(dx, dy);
}
// Moves snake forward, that is, moves each cell from tail to head
// to its next neighbor, and moves head one cell in its direction
void Snake::move_forward()
{
// Move each cell from tail to head to its next neighbour
for (int i = body.size() - 1; i > 0; --i) {
body[i].move(-body[i].point(0).x, -body[i].point(0).y); // Move to initial point
body[i].move(body[i - 1].point(0).x, body[i - 1].point(0).y); // Move to neigbhour's point
}
// Move head one cell in its direction
switch (head) {
case Direction::left: // Left-side
body[head_ind].move(-body[head_ind].width(), 0);
break;
case Direction::up: // Up-side
body[head_ind].move(0, -body[head_ind].height());
break;
case Direction::right: // Right-side
body[head_ind].move(body[head_ind].width(), 0);
break;
case Direction::down: // Down-side
body[head_ind].move(0, body[head_ind].height());
break;
}
set_point(0, body[head_ind].point(0)); // Update location of snake's head
}
// Grows snake in length, that is, adds one cell to its tail
void Snake::grow_length()
{
const Point tail = body[body.size() - 1].point(0); // Tail's coordinate
move_forward();
// Add new cell into body at previous tail's location
body.push_back(new Rectangle{ tail, body[head_ind].width(), body[head_ind].height() });
// Set same parameters for new tail as for all body
body[body.size() - 1].set_color(color());
body[body.size() - 1].set_fill_color(fill_color());
body[body.size() - 1].set_style(style());
}
// Shrinks snake in length, that is, removes num cells from its body, starting with tail
void Snake::shrink_length(int num)
{
if (num >= body.size()) // Error handling
throw invalid_argument("Bad Snake: can't shrink to non-positive length");
constexpr bool own = true; // Cells are owned by body
// Remove num cells from snake's body
for (int i = 0; i < num; ++i)
body.pop_back(own);
}
// Sets c as color of snake's lines
void Snake::set_color(Color c)
{
Shape::set_color(c);
// Set c as color of lines to each cell of body
for (int i = 0; i < body.size(); ++i)
body[i].set_color(c);
}
// Sets c as fill color of snake's body
void Snake::set_fill_color(Color c)
{
Shape::set_fill_color(c);
// Set c as fill color to each cell of body
for (int i = 0; i < body.size(); ++i)
body[i].set_fill_color(c);
}
// Sets c as fill color of snake's head
void Snake::head_set_fill_color(Color c)
{
if (body.begin() == body.end()) // Error handling
throw out_of_range("Bad Snake: can't set fill color to head of empty snake");
body[head_ind].set_fill_color(c);
}
// Sets ls as line style of snake's body
void Snake::set_style(Line_style ls)
{
Shape::set_style(ls);
// Set ls as line style to each cell of body
for (int i = 0; i < body.size(); ++i)
body[i].set_style(ls);
}
// Sets d as direction of snake's head
void Snake::set_direction(Direction d)
{
constexpr int opposite_diff = 2; // Module of opposite direction's difference
// Difference of directions
const int diff = abs(static_cast<int>(head) - static_cast<int>(d));
if (diff != opposite_diff) // Set direction if it's not opposite
head = d;
}
// Gets snake's head
const Rectangle& Snake::body_head() const
{
if (body.cbegin() == body.cend()) // Error handling
throw out_of_range("Bad Snake: can't get head of empty snake");
return body[head_ind];
}
// Determines either cell is one of snake's body's cells
bool Snake::is_body(const Rectangle& cell) const
{
// Search for cell in snake's body, located same as cell, and compare parameters
return find_if(body.cbegin(), body.cend(), [&cell](const Rectangle* rect)
{ return rect->point(0) == cell.point(0); }) != body.cend()
&& body[0].width() == cell.width() && body[0].height() == cell.height();
}
// Determines either cell is one of snake's body's cells, except its head
bool Snake::is_body_except_head(const Rectangle& cell) const
{
// Search for cell in snake's body, located same as cell, except snake's head, and compare parameters
return body.cbegin() != body.cend() ? find_if(next(body.cbegin()), body.cend(),
[&cell](const Rectangle* rect) { return rect->point(0) == cell.point(0); }) != body.cend()
&& body[0].width() == cell.width() && body[0].height() == cell.height() : false;
}
//------------------------------------------------------------------------------
// Moves rect randomly in range [xy.x; xy.x + w] for x-coordinate and [xy.y; xy.y + h] for
// y-coordinate, with xy as original point, w as width of range and h as height of range
void random_move(Rectangle& rect, Point xy, int w, int h)
{
if (w < 0 || h < 0) // Error handling
throw invalid_argument("Bad random_move: invalid range for coordinates");
// Move to original location, that is, xy
rect.move(-(rect.point(0).x - xy.x), -(rect.point(0).y - xy.y));
rect.move(rect.width() * randint(0, w / rect.width()), // Random x-coordinate
rect.height() * randint(0, h / rect.height())); // Random y-coordinate
}
//------------------------------------------------------------------------------
} // End of Graph_lib namespace
//------------------------------------------------------------------------------
질문
앞으로 코드를 개선하려면 어떻게해야합니까? 어떤 팁이라도 감사하지만 특히 코드 구조, 유연성 및 가독성에 대한 귀하의 생각을 확인하시기 바랍니다.
크레딧
시간과 노력에 감사드립니다.
답변
catch
처리 할 수 없는 오류는하지 마십시오
유용한 일을 할 수 있다면 예외를 포착해야합니다. 그러나 오류 메시지를 인쇄 한 다음 즉시 종료하는 것은 유용하지 않습니다. 예외를 포착하지 않으면 어쨌든 기본적으로 발생합니다.
창 위치를 지정하지 마십시오
창 관리자가 창의 초기 위치를 결정하도록해야합니다. 사용자가 창을 원하는 위치를 더 잘 알고 있으며 현재 마우스 커서가있는 위치, 화면에 아직 사용되지 않은 공간이있는 위치 등과 같은 휴리스틱 스를 사용할 수 있습니다.
게임 메뉴 버튼 멤버 변수 만들기
다른 버튼이의 멤버 변수 일 때 세 개의 버튼이으로 game_menu
생성 된 이유는 무엇 입니까? 코드를 살펴보면에 대한 참조를받는 오버로드가있는 것 같으므로 작동해야하며 더 일관성이 있습니다.new
Snake_window
Window::attach()
Button
생성자에서 도움말 텍스트를 이동하고 원시 문자열 리터럴을 사용합니다.
의 생성자는 Snake_window()
대부분 창에 위젯을 추가하기위한 논리를 포함하지만 그 중간에 거대한 도움말 텍스트 덩어리가 있습니다. 이 함수에서 텍스트 자체를 이동하여 정적 변수에 넣는 것이 합리적 일 수 있습니다. 또한 원시 문자열 리터럴을 사용할 수 있으므로 더 이상 따옴표 문자를 작성하고 줄 바꿈을 이스케이프 할 필요가 없습니다.
static const char *help_text =
R"( SNAKE GAME
Snake is a video game concept where the player maneuvers a line
that grows in length, with the line itself being a primary obstacle.
The concept originated in the 1976 arcade game Blockade.
...
ADDITIONAL NOTES
Good luck on the game, try to eat as much as you can!
)";
...
Snake_window::Snake_window(...)
: ...
{
...
help_box.put(help_text);
...
}
디버그 문 제거
에서 Snake_window::handle()
당신이 뭔가 매번를 인쇄하는 뱀 방향을 변경합니다. 이것을 디버깅에 사용한 것 같습니까? 프로덕션 코드에서이를 제거해야합니다. cout
제거해야하는 코드를 인쇄하는 다른 예제 가 있습니다.
이름을 reference_to<Snake_window>(pw)
FLTK가 비 정적 멤버 함수에 대한 콜백을 지원하지 않는 것은 조금 안타깝습니다. 이제 reference_to<Snake_window>(pw)
클래스 인스턴스를 얻기 위해 작성 해야합니다. 그러나 그것은 약간 길고 비밀 스럽습니다. self
합리적으로 자명 한 이름을 지정 하는 것이 좋습니다.
void Snake_window::cb_pause(Address, Address pw)
{
auto self = reference_to<Snake_window>(pw);
self.pause();
self.game();
}
뱀의 몸
이것은 끔찍하게 잘못된 곳입니다. 본문이 어떻게 선언되는지 살펴 보겠습니다.
Vector_ref<Rectangle> body;
나는 그것이 Vector_ref
일종의 래퍼 라는 것을 봅니다 std::vector<T *>
. 하지만 왜 Rectangle
포인터 또는 참조로 s 를 저장해야 합니까? GitHub 저장소를 보면에서 Rectangle
파생 된 것처럼 보이지만 Shape
복사 생성자와 복사 할당 연산자를 삭제했습니다. 그 이유를 모르겠습니다. 누군가가 bare를 복사하지 못하도록 Shape
하려면 다음 protected
과 같이 복사 작업을 수행하는 것이 좋습니다 .
class Shape {
...
protected:
Shape(const Shape &other) = default;
Shape &operator=(const Shape &other) = default;
...
};
일단 가지고 있으면 다음 Rectangle
과 같이 s 벡터를 만들 수 있습니다 .
std::vector<Rectangle> body;
그러나 아래에서 논의 할 다른 문제가 있습니다.
a std::deque<>
를 사용하여 신체 위치 저장
벡터를 사용하고 있으며, 꼬리 부분을 제거하고 새 머리 부분을 추가 할 때마다 몸의 모든 위치를 이동해야합니다. 꽤 비싼 작업입니다. 자신의 for
루프는 각 지점을 두 번 이동하기 때문에 매우 비효율적입니다. 를 사용하는 경우 다음 과 같이 std::vector
사용할 수 있습니다 .pop_back()
emplace()
void Snake::move_forward() {
body.pop_back();
body.emplace(body.begin(), { /* new head constructor arguments */ });
}
그러나 그러면 std::vector
모든 요소가 변경됩니다. 이상적으로 원하는 것은 모든 신체 위치를 그대로 유지 한 다음 꼬리를 제거하고 O (1) 시간에 새 머리를 추가하는 것입니다. a 중 하나를 사용하여 수행 할 수 std::list있지만 a와 같이 작동하는 것을 원한다면 std::vector
a std::deque가 이상적입니다. 코드는 다음과 같습니다.
void Snake::move_forward() {
body.pop_back();
body.emplace_front({ /* new head constructor arguments */ });
}
다시 한번:
불필요하게 포인트를 이동하지 마십시오.
이 패턴이 여러 곳에서 사용되는 것을 봅니다.
fruit.move(-fruit.point(0).x, -fruit.point(0).y); // Top-left angle of window
fruit.move(fruit_xy.x, fruit_xy.y);
기본적으로 원하는 것은 과일 위치를로 설정하는 것 fruit_xy
입니다. Rectangle
원하는 위치를 직접 설정할 수 있는 멤버 함수를 만들면 다음과 같이 작성할 수 있습니다.
fruit.set_xy(fruit_xy);
신체 성장 단순화
먼저 뱀을 움직 인 다음 (오래된 꼬리를 제거하는) 몸을 키우는 별도의 기능을 갖는 대신, 이전 꼬리를 다시 추가하는 대신 Snake::move_forward()
선택적으로 꼬리를 제거하지 않도록 변경하는 것이 좋습니다. Snake
본문이 성장해야하는 요소 수 를 나타내는 멤버 변수를에 추가하여이를 수행합니다 .
class Snake {
...
public:
void grow(size_t length) { to_grow += length; }
private:
size_t to_grow;
};
그런 다음에서 다음 Snake::move_forward()
과 같이하십시오.
void Snake::move_forward() {
if (to_grow)
to_grow--;
else
body.pop_back();
body.emplace_front({ /* new head constructor arguments */ });
}
assert()
불가능한 일을 확인하는 데 사용
Snake
그 여부를 확인하는 몇 가지 멤버 함수를 봅니다 body.begin() == body.end()
. 몸의 길이가 0 인 경우에만 해당됩니다. 그러나 생성자는 Snake
1보다 작은 길이를 지정하면 이미 오류가 발생합니다. 따라서 원칙적으로 불필요한 지 확인합니다. 그러나 assert()
문 을 사용하여 가정을 인코딩하는 것이 좋습니다 . 그래야 이러한 가정을 디버그 빌드에서 확인할 수 있지만 다음과 같이 릴리스 빌드 속도가 느려지지는 않습니다.
#include <cassert>
...
const Rectangle &Snake::body_head() const {
assert(head_ind >= 0 && head_ind < body.size());
return body[head_ind];
}
body.front()
헤드 요소를 가져 오는 데 사용 하는 것이 더 간단하지만 다음 과 같이 작성합니다.
const Rectangle &Snake::body_head() const {
assert(!body.empty());
return body.front();
}
개인적으로,이 특별한 경우에 뱀의 몸길이가 항상 0이 아닌 것이 분명하다면, 나는 그 assert()
진술을 전혀 쓰지 않을 것입니다 . 코드를 복잡하게 만들고 Valgrind 와 같은 도구는 경계를 벗어난 오류도 포착 할 수 있습니다.
그럼에도 불구 Snake
하고 예외를 던지는 대신 길이 매개 변수를 확인하기 위해 생성자에서 assert를 사용합니다 .
일반적으로 Assert는 자신의 코드에 대한 가정을 확인하는 데 사용되어야합니다. 그러나 if (...)
조건이 사용자 입력에 따라 달라지는 경우에는 plus 일부 종류의 오류보고 (예 : 예외 발생)를 사용하십시오.