You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
495 lines
16 KiB
495 lines
16 KiB
/**
|
|
* \file
|
|
* \brief Implementation of maze solving
|
|
* \author Erich Styger, erich.styger@hslu.ch
|
|
* \license SPDX-License-Identifier: BSD-3-Clause
|
|
* This module is used to solve a line maze.
|
|
*/
|
|
|
|
#include "platform.h"
|
|
#if PL_CONFIG_APP_LINE_MAZE
|
|
#include "Maze.h"
|
|
#include "Turn.h"
|
|
#include "McuShell.h"
|
|
#include "McuRTOS.h"
|
|
#if PL_CONFIG_USE_LEDS
|
|
#include "leds.h"
|
|
#include "McuLED.h"
|
|
#endif
|
|
#include "LineFollow.h"
|
|
#include "LineHistory.h"
|
|
#include "McuEvents.h"
|
|
#include "McuUtility.h"
|
|
#include "PID.h"
|
|
#include "Reflectance.h"
|
|
#include "Shell.h"
|
|
#if PL_CONFIG_HAS_NVM_CONFIG
|
|
#include "NVM_Config.h"
|
|
#endif
|
|
#if PL_CONFIG_USE_BUZZER
|
|
#include "Buzzer.h"
|
|
#endif
|
|
|
|
#define MAZE_DEBUG_PRINT (0) /* careful: this will slow down the PID loop frequency! */
|
|
#define MAZE_DO_DEBUG_WAIT (0 /*|| PL_SLOWER_SPEED*/) /* if set to one, it will add some debug wait to stop the engines */
|
|
#if MAZE_DO_DEBUG_WAIT
|
|
#define MAZE_DO_DEBUG_WAIT_MS (500) /* waiting time in milliseconds */
|
|
#endif
|
|
|
|
#if MAZE_DO_DEBUG_WAIT /* debug mode */
|
|
static void DebugStopAndWait(void) {
|
|
static volatile bool waitHere = FALSE;
|
|
|
|
TURN_Turn(TURN_STOP, NULL);
|
|
vTaskDelay(MAZE_DO_DEBUG_WAIT_MS);
|
|
waitHere = TRUE;
|
|
}
|
|
#endif
|
|
|
|
#define MAZE_MAX_PATH 64 /* maximum number of turns in path */
|
|
static TURN_Kind path[MAZE_MAX_PATH];
|
|
static uint8_t pathLength;
|
|
static bool isSolved = FALSE;
|
|
static bool useLeftHandOnTheWallRule = TRUE;
|
|
|
|
bool MAZE_IsLeftHandRule(void) {
|
|
return useLeftHandOnTheWallRule;
|
|
}
|
|
|
|
uint8_t MAZE_SetHandRule(bool isLeft) {
|
|
const NVMC_RobotData *ptr;
|
|
NVMC_RobotData data;
|
|
uint8_t res;
|
|
|
|
res = ERR_OK;
|
|
useLeftHandOnTheWallRule = isLeft;
|
|
ptr = NVMC_GetRobotData();
|
|
if (ptr!=NULL && ptr->MazeLeftHandOnTheWall!=useLeftHandOnTheWallRule) {
|
|
data = *NVMC_GetRobotData();
|
|
res = NVMC_SaveRobotData(&data);
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/* used to convert path items while running backwards */
|
|
static TURN_Kind MirrorTurn(TURN_Kind turn) {
|
|
switch(turn) {
|
|
case TURN_LEFT90: return TURN_RIGHT90;
|
|
case TURN_RIGHT90: return TURN_LEFT90;
|
|
case TURN_LEFT180: return TURN_STRAIGHT;
|
|
case TURN_RIGHT180: return TURN_STRAIGHT;
|
|
case TURN_STRAIGHT: return TURN_STRAIGHT;
|
|
case TURN_STEP_LINE_FW: return TURN_STEP_LINE_BW;
|
|
case TURN_STEP_LINE_BW: return TURN_STEP_LINE_FW;
|
|
case TURN_STEP_POST_LINE_FW: return TURN_STEP_POST_LINE_BW;
|
|
case TURN_STEP_POST_LINE_BW: return TURN_STEP_POST_LINE_FW;
|
|
case TURN_STEP_LINE_FW_AND_POST_LINE: return TURN_STEP_LINE_BW_AND_POST_LINE;
|
|
case TURN_STEP_LINE_BW_AND_POST_LINE: return TURN_STEP_LINE_FW_AND_POST_LINE;
|
|
case TURN_STOP:
|
|
case TURN_FINISH:
|
|
default:
|
|
return TURN_STOP;
|
|
}
|
|
}
|
|
|
|
TURN_Kind MAZE_SelectTurnBw(REF_LineKind prev, REF_LineKind curr);
|
|
/*!
|
|
* \brief Performs a turn while doing backward line following.
|
|
* \return Returns TRUE while turn is still in progress.
|
|
*/
|
|
uint8_t MAZE_EvaluateTurnBw(void) {
|
|
REF_LineKind historyLineKind, currLineKind;
|
|
TURN_Kind turn;
|
|
|
|
HISTORY_Clear(); /* clear values */
|
|
HISTORY_SampleSensors(); /* store current values */
|
|
TURN_Turn(TURN_STEP_LINE_BW, HISTORY_SampleTurnStopFunction); /* make step over line */
|
|
historyLineKind = HISTORY_LineKind(); /* new read new values */
|
|
currLineKind = REF_GetLineKind(REF_LINE_KIND_MODE_MAZE);
|
|
#if MAZE_DEBUG_PRINT
|
|
SHELL_SendString((unsigned char*)" history: ");
|
|
SHELL_SendString((unsigned char*)REF_LineKindStr(historyLineKind));
|
|
SHELL_SendString((unsigned char*)" curr: ");
|
|
SHELL_SendString((unsigned char*)REF_LineKindStr(currLineKind));
|
|
SHELL_SendString((unsigned char*)"\r\n");
|
|
#endif
|
|
turn = MAZE_SelectTurnBw(historyLineKind, currLineKind);
|
|
if (turn==TURN_STOP) { /* should not happen here? */
|
|
LF_StopFollowing();
|
|
SHELL_SendString((unsigned char*)"stopped\r\n");
|
|
return ERR_FAILED; /* error case */
|
|
} else { /* turn or do something */
|
|
#if MAZE_DEBUG_PRINT
|
|
SHELL_SendString((unsigned char*)"bw turning ");
|
|
SHELL_SendString((unsigned char*)TURN_TurnKindStr(turn));
|
|
SHELL_SendString((unsigned char*)"\r\n");
|
|
#endif
|
|
TURN_Turn(TURN_STEP_LINE_FW_AND_POST_LINE, NULL); /* step over intersection */ /* ???? not good for going backward over -| */
|
|
TURN_Turn(turn, NULL); /* make turn */
|
|
MAZE_AddPath(MirrorTurn(turn));
|
|
MAZE_SimplifyPath();
|
|
return ERR_OK; /* turn finished */
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Performs a turn.
|
|
* \return Returns TRUE while turn is still in progress.
|
|
*/
|
|
uint8_t MAZE_EvaluteTurn(bool *finished, bool *deadEndGoBw) {
|
|
REF_LineKind historyLineKind, currLineKind;
|
|
TURN_Kind turn;
|
|
|
|
*finished = FALSE; /* defaults */
|
|
*deadEndGoBw = FALSE; /* default */
|
|
currLineKind = REF_GetLineKind(REF_LINE_KIND_MODE_MAZE);
|
|
if (currLineKind==REF_LINE_NONE) { /* nothing, must be dead end */
|
|
#if PL_GO_DEADEND_BW
|
|
TURN_Turn(TURN_STEP_LINE_BW, NULL); /* step back so we are again on the line for line following */
|
|
turn = TURN_STRAIGHT;
|
|
*deadEndGoBw = TRUE;
|
|
#else
|
|
turn = TURN_LEFT180;
|
|
#endif
|
|
} else {
|
|
HISTORY_Clear(); /* clear history values */
|
|
HISTORY_SampleSensors(); /* store current values */
|
|
#if PL_CONFIG_USE_QUADRATURE
|
|
TURN_Turn(TURN_STEP_LINE_FW_AND_POST_LINE, HISTORY_SampleTurnStopFunction); /* do the line and beyond in one step */
|
|
#else
|
|
TURN_Turn(TURN_STEP_LINE_FW, NULL); /* make forward step over line */
|
|
#endif
|
|
#if MAZE_DO_DEBUG_WAIT /* debug mode */
|
|
DebugStopAndWait();
|
|
#endif
|
|
historyLineKind = HISTORY_LineKind(); /* new read new values */
|
|
currLineKind = REF_GetLineKind(REF_LINE_KIND_MODE_MAZE);
|
|
turn = MAZE_SelectTurn(historyLineKind, currLineKind);
|
|
#if MAZE_DEBUG_PRINT
|
|
SHELL_SendString((unsigned char*)"selected turn: ");
|
|
SHELL_SendString((unsigned char*)TURN_TurnKindStr(turn));
|
|
SHELL_SendString((unsigned char*)"\r\n");
|
|
#endif
|
|
}
|
|
#if MAZE_DO_DEBUG_WAIT /* debug mode */
|
|
DebugStopAndWait();
|
|
#endif
|
|
if (turn==TURN_FINISH) {
|
|
*finished = TRUE;
|
|
SHELL_SendString((unsigned char*)"MAZE: finish area!\r\n");
|
|
#if PL_CONFIG_USE_BUZZER
|
|
BUZ_PlayTune(BUZ_TUNE_MAZE_DESTINATION);
|
|
#endif
|
|
return ERR_OK;
|
|
} else if (turn==TURN_STRAIGHT && *deadEndGoBw) {
|
|
MAZE_AddPath(TURN_LEFT180); /* would have been a turn around */
|
|
MAZE_SimplifyPath();
|
|
SHELL_SendString((unsigned char*)"MAZE: going backward\r\n");
|
|
return ERR_OK;
|
|
} else if (turn==TURN_STRAIGHT) {
|
|
MAZE_AddPath(turn);
|
|
MAZE_SimplifyPath();
|
|
SHELL_SendString((unsigned char*)"MAZE: going straight\r\n");
|
|
return ERR_OK;
|
|
} else if (turn==TURN_STOP) { /* should not happen here? */
|
|
LF_StopFollowing();
|
|
SHELL_SendString((unsigned char*)"MAZE: Failure, stopped!!!\r\n");
|
|
return ERR_FAILED; /* error case */
|
|
} else { /* turn or do something */
|
|
#if MAZE_DEBUG_PRINT
|
|
SHELL_SendString((unsigned char*)"turning ");
|
|
SHELL_SendString((unsigned char*)TURN_TurnKindStr(turn));
|
|
SHELL_SendString((unsigned char*)"\r\n");
|
|
#endif
|
|
#if !PL_CONFIG_USE_QUADRATURE /* if using quadrature, we stepped this piece already above */
|
|
if (turn==TURN_LEFT90 || turn==TURN_RIGHT90) {
|
|
TURN_Turn(TURN_STEP_POST_LINE_FW, NULL); /* step before doing the turn so we turn on the middle of the intersection */
|
|
}
|
|
#endif
|
|
TURN_Turn(turn, NULL); /* make turn */
|
|
MAZE_AddPath(turn);
|
|
MAZE_SimplifyPath();
|
|
return ERR_OK; /* turn finished */
|
|
}
|
|
}
|
|
|
|
static TURN_Kind RevertTurn(TURN_Kind turn) {
|
|
if (turn==TURN_LEFT90) {
|
|
turn = TURN_RIGHT90;
|
|
} else if (turn==TURN_RIGHT90) {
|
|
turn = TURN_LEFT90;
|
|
}
|
|
return turn;
|
|
}
|
|
|
|
/**
|
|
* \brief Reverts the path
|
|
*/
|
|
static void MAZE_RevertPath(void) {
|
|
int i, j;
|
|
TURN_Kind tmp;
|
|
|
|
if (pathLength==0) {
|
|
return;
|
|
}
|
|
j = pathLength-1;
|
|
i = 0;
|
|
while(i<=j) {
|
|
tmp = path[i];
|
|
path[i] = RevertTurn(path[j]);
|
|
path[j] = RevertTurn(tmp);
|
|
i++; j--;
|
|
}
|
|
}
|
|
|
|
TURN_Kind MAZE_SelectTurn(REF_LineKind prev, REF_LineKind curr) {
|
|
if (prev==REF_LINE_NONE && curr==REF_LINE_NONE) { /* dead end */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_RIGHT180; /* make U turn */
|
|
} else {
|
|
return TURN_LEFT180; /* make U turn */
|
|
}
|
|
} else if (prev==REF_LINE_FULL && curr==REF_LINE_NONE) { /* 'T' */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_LEFT90;
|
|
} else {
|
|
return TURN_RIGHT90;
|
|
}
|
|
} else if (prev==REF_LINE_RIGHT && curr==REF_LINE_NONE) { /* right turn */
|
|
return TURN_RIGHT90;
|
|
} else if (prev==REF_LINE_LEFT && curr==REF_LINE_NONE) { /* left turn */
|
|
return TURN_LEFT90;
|
|
} else if (prev==REF_LINE_FULL && curr==REF_LINE_STRAIGHT) { /* '+' intersection */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_LEFT90;
|
|
} else {
|
|
return TURN_RIGHT90;
|
|
}
|
|
} else if (prev==REF_LINE_FULL && curr==REF_LINE_FULL) { /* finish area */
|
|
return TURN_FINISH;
|
|
} else if (prev==REF_LINE_RIGHT && curr==REF_LINE_STRAIGHT) { /* forward and right turn */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_STRAIGHT;
|
|
} else {
|
|
return TURN_RIGHT90;
|
|
}
|
|
} else if (prev==REF_LINE_LEFT && curr==REF_LINE_STRAIGHT) { /* forward and left turn */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_LEFT90;
|
|
} else {
|
|
return TURN_STRAIGHT;
|
|
}
|
|
}
|
|
return TURN_STOP; /* error case */
|
|
}
|
|
|
|
TURN_Kind MAZE_SelectTurnBw(REF_LineKind prev, REF_LineKind curr) {
|
|
if (prev==REF_LINE_LEFT && curr==REF_LINE_NONE) { /* was turn to left */
|
|
return TURN_LEFT90;
|
|
} else if (prev==REF_LINE_RIGHT && curr==REF_LINE_NONE) { /* was turn to right */
|
|
return TURN_RIGHT90;
|
|
} else if (prev==REF_LINE_RIGHT && curr==REF_LINE_STRAIGHT) { /* |- */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_RIGHT90;
|
|
} else {
|
|
return TURN_LEFT180;
|
|
}
|
|
} else if (prev==REF_LINE_LEFT && curr==REF_LINE_STRAIGHT) { /* -| */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_RIGHT180;
|
|
} else {
|
|
return TURN_LEFT90;
|
|
}
|
|
} else if (prev==REF_LINE_FULL && curr==REF_LINE_NONE) { /* T upside-down */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_RIGHT90;
|
|
} else {
|
|
return TURN_LEFT90;
|
|
}
|
|
} else if (prev==REF_LINE_FULL && curr==REF_LINE_STRAIGHT) { /* '+' intersection */
|
|
if (MAZE_IsLeftHandRule()) {
|
|
return TURN_RIGHT90;
|
|
} else {
|
|
return TURN_LEFT90;
|
|
}
|
|
}
|
|
return TURN_STOP; /* error case */
|
|
}
|
|
|
|
void MAZE_SetSolved(void) {
|
|
isSolved = TRUE;
|
|
MAZE_RevertPath();
|
|
MAZE_AddPath(TURN_STOP); /* add an action to stop */
|
|
}
|
|
|
|
bool MAZE_IsSolved(void) {
|
|
return isSolved;
|
|
}
|
|
|
|
void MAZE_AddPath(TURN_Kind kind) {
|
|
if (pathLength<MAZE_MAX_PATH) {
|
|
path[pathLength] = kind;
|
|
pathLength++;
|
|
} else {
|
|
/* error! */
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* \brief Performs path simplification.
|
|
* The idea is that whenever we encounter x-TURN_RIGHT180-x or x-TURN_LEFT180-x, we simplify it by cutting the dead end.
|
|
* For example if we have TURN_LEFT90-TURN_RIGHT180-TURN_LEFT90, this can be simplified with TURN_STRAIGHT.
|
|
*/
|
|
void MAZE_SimplifyPath(void) {
|
|
/* implemented for left-hand-on-the-wall rule for now! */
|
|
uint16_t totalAngle, i;
|
|
|
|
if (pathLength<3) { /* we need at least 3 entries for simplification */
|
|
return;
|
|
}
|
|
if (!(path[pathLength-2] == TURN_RIGHT180 || path[pathLength-2] == TURN_LEFT180)) {
|
|
/* we simplify only if second-to-last is a dead-end */
|
|
return;
|
|
}
|
|
totalAngle = 0;
|
|
for (i=1; i<=3; i++) {
|
|
switch(path[pathLength-i]) {
|
|
case TURN_LEFT90: totalAngle += 270; break;
|
|
case TURN_RIGHT90: totalAngle += 90; break;
|
|
case TURN_LEFT180: totalAngle += 180; break;
|
|
case TURN_RIGHT180: totalAngle += 180; break;
|
|
default:
|
|
/* would be an error? */
|
|
break;
|
|
} /* switch */
|
|
}
|
|
totalAngle %= 360; /* make sure angle is within 0...360 degrees */
|
|
switch(totalAngle) {
|
|
case 0: path[pathLength-3] = TURN_STRAIGHT; break;
|
|
case 90: path[pathLength-3] = TURN_RIGHT90; break;
|
|
case 180: path[pathLength-3] = TURN_LEFT180; break;
|
|
case 270: path[pathLength-3] = TURN_LEFT90; break;
|
|
}
|
|
pathLength -= 2; /* was able to cut the path by 2 entries :-) */
|
|
}
|
|
|
|
static void MAZE_PrintHelp(const McuShell_StdIOType *io) {
|
|
McuShell_SendHelpStr((unsigned char*)"maze", (unsigned char*)"Group of maze following commands\r\n", io->stdOut);
|
|
McuShell_SendHelpStr((unsigned char*)" help|status", (unsigned char*)"Shows maze help or status\r\n", io->stdOut);
|
|
McuShell_SendHelpStr((unsigned char*)" clear", (unsigned char*)"Clear the maze solution\r\n", io->stdOut);
|
|
McuShell_SendHelpStr((unsigned char*)" rule (left|right)", (unsigned char*)"Left or Right-hand-on-the-wall rule\r\n", io->stdOut);
|
|
}
|
|
|
|
static void MAZE_PrintStatus(const McuShell_StdIOType *io) {
|
|
int i;
|
|
|
|
McuShell_SendStatusStr((unsigned char*)"maze", (unsigned char*)"maze solving status\r\n", io->stdOut);
|
|
McuShell_SendStatusStr((unsigned char*)" rule", MAZE_IsLeftHandRule()?(unsigned char*)"left-hand-on-the-wall\r\n":(unsigned char*)"right-hand-on-the-wall\r\n", io->stdOut);
|
|
McuShell_SendStatusStr((unsigned char*)" solved", MAZE_IsSolved()?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut);
|
|
McuShell_SendStatusStr((unsigned char*)" path", (unsigned char*)"(", io->stdOut);
|
|
McuShell_SendNum8u(pathLength, io->stdOut);
|
|
McuShell_SendStr((unsigned char*)") ", io->stdOut);
|
|
for(i=0;i<pathLength;i++) {
|
|
McuShell_SendStr(TURN_TurnKindStr(path[i]), io->stdOut);
|
|
McuShell_SendStr((unsigned char*)" ", io->stdOut);
|
|
}
|
|
McuShell_SendStr((unsigned char*)"\r\n", io->stdOut);
|
|
}
|
|
|
|
uint8_t MAZE_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) {
|
|
uint8_t res = ERR_OK;
|
|
|
|
if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"maze help")==0) {
|
|
MAZE_PrintHelp(io);
|
|
*handled = TRUE;
|
|
} else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"maze status")==0) {
|
|
MAZE_PrintStatus(io);
|
|
*handled = TRUE;
|
|
} else if (McuUtility_strcmp((char*)cmd, (char*)"maze clear")==0) {
|
|
MAZE_ClearSolution();
|
|
*handled = TRUE;
|
|
} else if (McuUtility_strcmp((char*)cmd, (char*)"maze rule left")==0) {
|
|
useLeftHandOnTheWallRule = TRUE;
|
|
*handled = TRUE;
|
|
} else if (McuUtility_strcmp((char*)cmd, (char*)"maze rule right")==0) {
|
|
useLeftHandOnTheWallRule = FALSE;
|
|
*handled = TRUE;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
TURN_Kind MAZE_GetSolvedTurn(uint8_t *solvedIdx) {
|
|
if (*solvedIdx < pathLength) {
|
|
return path[(*solvedIdx)++];
|
|
} else {
|
|
return TURN_STOP;
|
|
}
|
|
}
|
|
|
|
void MAZE_ClearSolution(void) {
|
|
isSolved = FALSE;
|
|
pathLength = 0;
|
|
}
|
|
|
|
#if 0
|
|
typedef enum {
|
|
MAZE_STATE_INIT,
|
|
MAZE_STATE_IDLE,
|
|
MAZE_STATE_FOLLOW_LINE
|
|
} MazeStateType;
|
|
|
|
static MazeStateType mazeState = MAZE_STATE_INIT;
|
|
|
|
static void MAZE_StateMachine(void) {
|
|
switch (mazeState) {
|
|
case MAZE_STATE_INIT:
|
|
#if PL_CONFIG_USE_LEDS
|
|
McuLED_On(LEDS_Left);
|
|
McuLED_On(LEDS_Right);
|
|
#endif
|
|
if (REF_CanUseSensor()) {
|
|
mazeState = MAZE_STATE_IDLE;
|
|
}
|
|
break;
|
|
case MAZE_STATE_IDLE:
|
|
if (!REF_CanUseSensor()) { /* sensor not available any more? */
|
|
mazeState = MAZE_STATE_INIT;
|
|
}
|
|
#if PL_CONFIG_USE_LEDS
|
|
McuLED_Off(LEDS_Left);
|
|
McuLED_On(LEDS_Right);
|
|
#endif
|
|
if (EVNT_EventIsSet(EVNT_MAZE_BUTTON_PRESS)) {
|
|
EVNT_ClearEvent(EVNT_MAZE_BUTTON_PRESS);
|
|
LF_StartFollowing();
|
|
mazeState = MAZE_STATE_FOLLOW_LINE;
|
|
}
|
|
break;
|
|
case MAZE_STATE_FOLLOW_LINE:
|
|
#if PL_CONFIG_USE_LEDS
|
|
McuLED_Off(LEDS_Left);
|
|
McuLED_Off(LEDS_Right);
|
|
#endif
|
|
if (!LF_IsFollowing()) {
|
|
mazeState = MAZE_STATE_IDLE;
|
|
}
|
|
if (EVNT_EventIsSet(EVNT_MAZE_BUTTON_PRESS)) {
|
|
EVNT_ClearEvent(EVNT_MAZE_BUTTON_PRESS);
|
|
LF_StopFollowing();
|
|
mazeState = MAZE_STATE_IDLE;
|
|
}
|
|
break;
|
|
} /* switch */
|
|
}
|
|
#endif
|
|
|
|
void MAZE_Init(void) {
|
|
const NVMC_RobotData *ptr;
|
|
|
|
ptr = NVMC_GetRobotData();
|
|
if (ptr!=NULL) {
|
|
(void)MAZE_SetHandRule(ptr->MazeLeftHandOnTheWall);
|
|
}
|
|
MAZE_ClearSolution();
|
|
}
|
|
#endif /* PL_CONFIG_APP_LINE_MAZE */
|
|
|