我正在用C ++编写一个Snake程序(使用JNI进行可视化),有效的移动按钮是 left (逆时针旋转90°)或右(移动顺时针90°)。
在游戏循环的每个循环中,我从GUI界面检索一个关键事件,并根据该关键事件移动我的蛇,这就是我这样做的方式:
if(key_event != NULL){
if(key_event == LEFT){
if(moveDirection == UP || moveDirection == DOWN){
moveDirection = LEFT;
(moveDirection == UP) ? /*change turn after head*/ : /*change turn after head*/;
} //else if (moveDir == LEFT || RIGHT)
} //else if (key_event == RIGHT)
//...
}
if with:/*change turn after head*/
是因为如果蛇正在向向下移动并向向左移动那么转弯时会有另一个图形,然后它会变为上,然后左。
这会导致很多if语句并且不是很容易读,所以我想知道是否有一种通用的方法来解决这样的嵌套if语句。
编辑:
key_event和moveDirection是枚举。
答案 0 :(得分:6)
感觉Finite State Machine就像你需要的一样。您可以将键定义为事件,将蛇位置定义为状态,并根据事件(按下的键)描述从一种状态到另一种状态的转换。这将解决问题并使用转换表而不是嵌套的if
语句,并使您的实现不易出错。 Boost Statechart Library可以帮助您快速实施。
答案 1 :(得分:5)
我个人还有其他功能来应用此举。
if(key_event != NULL){
if(key_event == LEFT){
moveLeft();
}
...
}
void moveLeft()
{
switch(moveDirection)
{
case UP:
case DOWN:
moveDirection = LEFT;
break;
case ...
}
}
我使用switch case,在我看来这个例子更易读,而不是if .. else if .. else if ...
关键是,当你有嵌套循环时,看看你是否可以将其中的一些分解为一个函数来简化问题。
我希望这会有所帮助。
答案 2 :(得分:3)
如果将if检查的部分考虑到函数中,很可能代码将更具可读性。如果没有看到正在完成的工作的完整背景,很难肯定地说,但get_new_direction(event, direction)
之类的东西可能是一个好的开始。当if检查的组件是命名良好的函数时,它将有助于可读性和可能的嵌套级别。
答案 3 :(得分:2)
简单的查找表可以提供帮助:
enum dir { UP, RIGHT, DOWN, LEFT };
dir lturn[] = {LEFT, UP, RIGHT, DOWN};
dir rturn[] = {RIGHT, DOWN, LEFT, UP};
这样你就可以写
了dir curr_dir;
if (moveDirection == LEFT)
curr_dir = lturn[curr_dir];
if (moveDirection == RIGHT)
curr_dir = rturn[curr_dir];
答案 4 :(得分:1)
如果你的key_event是一个int或char,你可以使用一个可能更具可读性的switch语句。
if (moveDirection == UP || moveDirection == DOWN)
{
switch (key_event)
{
case LEFT:
//Turn snake left
break;
case RIGHT:
//Turn snake right
break;
default:
//Invalid turn, do nothing
break;
}
}
else if (moveDirection == LEFT || moveDirection == RIGHT)
{
switch (key_event)
{
case UP:
//Turn snake up
break;
case DOWN:
//Turn snake down
break;
default:
//Invalid turn, do nothing
break;
}
}
答案 5 :(得分:1)
嗯,显然你可以用这样的东西消除第一个“if”:
if( keyEvent == null )
return;
接下来,使用以下内容:
Action defaultAction = TurnLeftAction;
if( keyEvent == Up )
defaultAction = TurnUpAction;
else if( keyEvent == Down)
defaultAction = TurnDownAction;
else
defaultAction = TurnRightAction;
defaultAction.Execute();
我希望你能抓住这个想法:)
答案 6 :(得分:1)
您可以使用对象执行此操作:
class MoveState {
public:
MoveState* getNextState(MoveDirection change);
GraphicsStuff* getDirectionChangeGraphics(MoveDirection change);
void setNextState(MoveDirection change, MoveState* nextState, GraphicsStuff* graphics);
}
然后你有MoveState
的实例代表每个运动方向。您的外部代码可以是:
MoveState STATES[4];
void init() {
STATES[UP].setNextState(LEFT, &STATES[LEFT], whatever);
STATES[UP].setNextState(RIGHT, &STATES[RIGHT], whatever);
STATES[DOWN].setNextState(LEFT, &STATES[RIGHT], whatever);
STATES[DOWN].setNextState(RIGHT, &STATES[LEFT], whatever);
...
}
void handleInput() {
...
if(key_event != NULL){
MoveDirection change = convertEventToDirection(key_event);
MoveState* nextState = currentState->getNextState(change);
if (nextState != NULL) {
drawCoolGraphics(currentState->getDirectionChangeGraphics(change);
currentState = nextState;
}
}
}
您也可以通过为每个移动方向子类化MoveState来完成此操作。这取决于您想要放置代码的位置以及它需要的可扩展性。子类化更灵活,但在这种情况下可能过度。
这应该不易出错,因为你可以比一个大的if-then-else或switch语句更容易地单元测试每个MoveState实例。
编辑:嘿。这基本上是@Vlad建议的简洁版本。答案 7 :(得分:1)
我建议将蛇的方向表示为矢量,四个方向为(1 0) - 右,( - 1 0) - 左,(0 1) - 向上和(0 -1)向下。这将消除程序中的大多数开关,因为使蛇移动是通过简单的数学处理,而不是通过将任意枚举值映射到移动。
struct Vector2D
{
int x, y;
Vector2D(int x, int y);
...
};
然后左右转动:
Vector2D turn_left(Vector2D direction)
{
return Vector2D(-direction.y, direction.x);
}
Vector2D turn_right(Vector2D direction)
{
return Vector2D(direction.y, -direction.x);
}
密钥将由以下方式处理:
if (key_event == LEFT) snake_direction = turn_left(snake_direction);
else if (key_event == RIGHT) snake_direction = turn_right(snake_direction);
答案 8 :(得分:0)
您可以使用跳转表,假设键事件从零开始并且是整数类型,并且此函数是非静态的。然而,它有很多语义开销,以及时间和空间。一个好的转换案例可能是真正最简单的选择。
答案 9 :(得分:0)
这个问题有点老了,但我想贡献一点。我个人更喜欢早期回报,以避免嵌套ifs。同样针对您的情况,我使用映射。我会像下面这样写代码。
if(key_event == NULL)
return;
static const map<pair<int , int> , int> directionMapping = {
make_pair(make_pair(LEFT , UP) , 180) ,
make_pair(make_pair(LEFT , DOWN) , 0) ,
make_pair(make_pair(LEFT , RIGHT) , 90) ,
make_pair(make_pair(LEFT , LEFT) , 270) ,
make_pair(make_pair(RIGHT , UP) , 0) ,
make_pair(make_pair(RIGHT , DOWN) , 180) ,
make_pair(make_pair(RIGHT , RIGHT) , 270) ,
make_pair(make_pair(RIGHT , LEFT) , 90)
};
auto newDirection = directionMapping.at(make_pair(key_event , current_direction));
cout << "New direction is " << newDirection << endl;
它更具可读性,没有嵌套,没有深度,更易于维护。例如,如果您还希望支持UP和DOWN箭头键,则只需在映射中添加一些对即可。
可以使用映射执行不同的过程。看这个
if(type == "Foo")
//Do something
else if(type == "Bar")
//Do another thing
else if(type == "Nothing")
//Do nothing
这种映射方式,在c ++ 11之后受支持。
static const map<string , function<void()>> fMapping = {
make_pair("Foo" , [](){ cout << "I am doing something"; }),
make_pair("Bar" , [](){ cout << "I am doing another thing"; }) ,
make_pair("Nothing" , [](){ cout << "I am doing nothig"; })
};
string type = "Foo";
fMapping.at(type)();
请注意,这种映射在Factory类中非常有用。