1

Тема: Рух символа стрілками в консолі

1. Чи можна переміщати символ без перемалювання екрану?
2. Як взагалі в С++ запрограмувати реакцію на клавіатуру? Передивилась купу посилань - всі пишуть по-різному, і ніде нічого не зрозуміло.

2

Re: Рух символа стрілками в консолі

2) Я так розумію ти хочеш створити консольну гру якщо так то подивись на GetAsyncKeyState()
Наприклад

#include <Windows.h>
...
if (GetAsyncKeyState(0x57))//w,ц
{
//Code
}

getch() - зупиняє консольну програму до натиснення клавіши
наприклад

#include <conio.h>
...
if(getch()=='w')
{
//code
}

3

Re: Рух символа стрілками в консолі

1. Чи можна переміщати символ без перемалювання екрану?

Малось на увазі «перемалювати символ»? Так, можна, хоча й загальної методики для всіх систем і всіх випадків не існує.

Якщо символ, який треба замінити, лежить у тому ж рядку екрана, де й курсур, достатньо підвести курсор у потрібне місце (виводячи символ '\010', який не друкується, а лише переміщує курсор на одну позицію назад) і надрукувати на тому місці те, що нам треба. На жаль, це не працює, якщо символ лежить в іншому рядку.

Взагалі, існує така чудова річ, як ANSI ескейп-коди (які дозволяють, зокрема, рухати курсор вгору/вниз, виводячи послідовності керуючих символів). На жаль, це годиться переважно для ніксів — віндова консоль ці коди не підтримує (хоча й існують сторонні розробки, такі як ansicon, що додають цю можливість).

У часи ДОС практикувався прямий запис у відеобуфер текстового режиму (який являв собою область пам'яті з певною адресою — задавши цю адресу вказівникові як числове значення, можна було працювати з монітором як масивом, де кожна комірка містить код символа та його колір). Однак, це дуже низькорівневий підхід, і для програм під 32- та 64-бітні системи він, схоже, неактуальний...

На жаль, я не знаю, як ця проблема вирішується цивілізовано на C++ під віндою. Треба погуглити. Поки що бачу можливе вирішення в вигляді використання ansi-кодів і запуску програми через ansicon, але, очевидно, має існувати якась бібліотека, призначена спеціально для маніпуляцій з консоллю.

2. Як взагалі в С++ запрограмувати реакцію на клавіатуру?

getch()
Причому, натискання стрілок (та ряду інших спеціальних клавіш та клавішних комбінацій) сприймається цією функцією як два символи (тобто, перший раз getch() поверне символ з кодом 0 або 224 (в залежності від того, було використано числову клавіатуру чи окремо розміщені стрілки), другий раз — код, що ідентифікує стрілку. (Не впевнений, чи стосується це всіх компіляторів та систем, але gcc (MinGW) дає в мене такий результат).

Подякували: 0xDADA11C71

4

Re: Рух символа стрілками в консолі

1. https://msdn.microsoft.com/uk-ua/librar … s.85).aspx
2. https://msdn.microsoft.com/uk-ua/librar … s.85).aspx
Це за умови, що ви пишете під Windows (ви цього не стверджували).

5 Востаннє редагувалося pika1989 (09.08.2015 19:47:41)

Re: Рух символа стрілками в консолі

Зробила таким чином:

short getKeyPressed()
{
    if (GetAsyncKeyState(VK_UP) < 0)
        return UP;
    else if (GetAsyncKeyState(VK_DOWN) < 0)
        return DOWN;
    else if (GetAsyncKeyState(VK_LEFT) < 0)
        return LEFT;
    else if (GetAsyncKeyState(VK_RIGHT) < 0)
        return RIGHT;
    else if (GetAsyncKeyState(VK_ESCAPE) < 0)
        return ESCAPE;
    else if (GetAsyncKeyState(VK_RETURN) < 0)
        return ENTER;
    else
        return -1;
}
 
int main()
{
    srand(unsigned(time(0)));

    for (int i = 0; i < FIELD_SIZE; i++)
        for (int j = 0; j < FIELD_SIZE; j++)
            game_field[i][j] = ' ';

    short key;
    bool exit = false, action = false;
    paintGameField();
    do {
        // take keyboard input
        key = getKeyPressed();
        // do something - move_cursor, put X or O, etc.
        switch (key)
        {
        case ESCAPE:
            exit = true;
            break;
        case ENTER:
            break;
        default:
            action = movePointer(key);
        }
        if (action)
        {
            system("cls");
            paintGameField();
        }
        Sleep(100);
    } while (!exit);
    return 0;
}

і все, здається, працює... Але:
1) чи можна отримати відразу, яка кнопка натиснута, а не перераховувати коди?
2) оновлення екрану дуже помітно. Як це виправити?

6 Востаннє редагувалося koala (09.08.2015 20:25:52)

Re: Рух символа стрілками в консолі

1. Якщо хочете, то PeekConsoleInput та ReadConsoleInput. Але ваш варіант простіший, повірте.
2. По-перше, ви оновлюєте екран за допомогою

system("cls");

Щоб ви зрозуміли, що відбувається: воно створює новий процес cmd.exe (який явно більший за ваш проект), передає йому команду cls, cmd.exe обробляє цю команду, обережно і ретельно (і повільно) чистить всі екранні буфери і завершується. Це все відбувається ДУЖЕ ДОВГО - настільки, що ви встигаєте це помітити. Можете спробувати замінити це рекомендованою M$ функцією.
По-друге, ви використовуєте cout. cout працює непогано, але через те, що деякі програмісти використовують printf-и, вимушений із ними синхронізуватися, що його сповільнює; також, можливо, ви використовуєте endl, що сповільнює його ще більше (оскільки цей маніпулятор не тільки виводить новий рядок, а й вимагає виводу всього буферу негайно, а не коли захоче сама консоль). Додайте на початок рядок

std::cout.sync_with_stdio(false);

і замініть всі endl на '\n'; сподіваюся, цього вистачить.

Якщо ж ні - замініть вивід прямими зверненнями до WinAPI WriteConsoleOutputCharacter, це дозволяє писати швидко і одразу в потрібне місце екрану. Тоді і оновлювати екран не треба буде - тільки потрібні символи. Але це потребуватиме певної переробки вашої програми.

7 Востаннє редагувалося koala (09.08.2015 20:45:12)

Re: Рух символа стрілками в консолі

Ну і
int watchedKeys[][ 2 ] = { { VK_UP    , UP     },
                           { VK_DOWN  , DOWN   },
                           { VK_LEFT  , LEFT   },
                           { VK_RIGHT , RIGHT  },
                           { VK_ESCAPE, ESCAPE },
                           { VK_RETURN, ENTER  } } ;

const int watchedKeysSize = sizeof( watchedKeys ) / sizeof( watchedKeys[ 0 ] ) ;

short getKeyPressed()
{
  for( int i = 0 ; i < watchedKeysSize ; ++i ) {
    if( GetAsyncKeyState( watchedKeys[ i ][ 0 ] < 0 ) {
      return watchedKeys[ i ][ 1 ] ;
    }
  }
  return -1 ;
}
Подякували: pika19891

8

Re: Рух символа стрілками в консолі

Я вам дуже дякую, але в мене виникло запитання: що нам дає ось цей рядок

const int watchedKeysSize = sizeof( watchedKeys ) / sizeof( watchedKeys[ 0 ] ) ;

і застосування отриманого значення в циклі?

9

Re: Рух символа стрілками в консолі

Розмір масиву. Це стандартна формула кількості елементів в масиві. Якщо захочете обробляти ще якісь кнопки, треба тільки масив підредагувати і все.

10

Re: Рух символа стрілками в консолі

Дякую всім, хто відгукнувся на мої питання. Завдяки вашим підказкам, в мене вийшло реалізувати, хай не ідеально, гру "Хрестики-нулики".
Можливо, комусь буде цікаво, а, можливо, і корисно:

Прихований текст
/* "Гра х0. Перший гравець вибирається випадковим чином. Можна використ глоб масив." */

#include <iostream>
#include <ctime>
//#include <string>
#include <windows.h>
//#include <conio.h>
#include <cstdio>

using namespace std;

// global variables
const int FIELD_SIZE = 3, WIN_LINE_LENGTH = 3;
enum key { UP, DOWN, LEFT, RIGHT, ENTER, ESCAPE };
char game_field[FIELD_SIZE][FIELD_SIZE];
int pointer_row = 0, pointer_col = 0;

// function prototypes
char pointer(int row, int col);
void drawRow(char symbol, char pointer);
int choiceFirstPlayer();
void paintGameField();
bool movePointer(int direction);
short getKeyPressed();
bool putPlayerMark(char player_mark);
bool vitalComputerMove(char mark, char computer_mark);
void computerMove(char player_mark, char computer_mark);
bool checkFreeCorner(char computer_mark);
bool checkFreeCell(char computer_mark);
int checkWinner(char computer_mark, char player_mark);


int main()
{
    cout.sync_with_stdio(false);
    srand(unsigned(time(0)));

    for (int i = 0; i < FIELD_SIZE; i++)
        for (int j = 0; j < FIELD_SIZE; j++)
            game_field[i][j] = ' ';

    // choose first player, set marks
    int first = choiceFirstPlayer();
    char player_mark, computer_mark;
    if (first == 0)
    {
        computer_mark = 'O';
        player_mark = 'X';
    }
    else
    {
        computer_mark = 'X';
        player_mark = 'O';
        computerMove(player_mark, computer_mark);
    }

    short key;
    bool exit = false, action = false;
    int winner;
    paintGameField();
    do {
        // take keyboard input
        key = getKeyPressed();
        // do something - move_cursor, put X or O, etc.
        switch (key)
        {
        case ESCAPE:
            exit = true;
            break;
        case ENTER:
            action = putPlayerMark(player_mark);
            if (action)
            {
                winner = checkWinner(computer_mark, player_mark);
                // if not winner, computerMove()
                if (winner == -1)
                {
                    computerMove(player_mark, computer_mark);
                    winner = checkWinner(computer_mark, player_mark);
                    if (winner != -1)
                        exit = true;
                }
                else
                    exit = true;
            }
            break;
        default:
            action = movePointer(key);
        }
        if (action)
        {
            system("cls");
            paintGameField();
        }
        // continue loop if game is not finished
        Sleep(50);
    } while (!exit);
    if (winner == 0)
        cout << "\n\tYOU LOSE\n";
    else if (winner == 1)
        cout << "\n\tYUO WIN\n";
    else
        cout << "\n\tDRAW\n";
    return 0;
}

int choiceFirstPlayer()
{
    int player = rand() % 2;
    return player;
}

short getKeyPressed()
{
    if (GetAsyncKeyState(VK_UP) < 0)
        return UP;
    else if (GetAsyncKeyState(VK_DOWN) < 0)
        return DOWN;
    else if (GetAsyncKeyState(VK_LEFT) < 0)
        return LEFT;
    else if (GetAsyncKeyState(VK_RIGHT) < 0)
        return RIGHT;
    else if (GetAsyncKeyState(VK_ESCAPE) < 0)
        return ESCAPE;
    else if (GetAsyncKeyState(VK_RETURN) < 0)
        return ENTER;
    else
        return -1;
}

void drawRow(int row)
{
    for (int i = 0; i < FIELD_SIZE; i++)
        cout << " ___" << " ";
    cout << endl;
    for (int i = 0; i < FIELD_SIZE; i++)
        cout << "|" << pointer(row, i) << "  |";
    cout << endl;
    for (int i = 0; i < FIELD_SIZE; i++)
        cout << "| " << game_field[row][i] << " |";
    cout << endl;
    for (int i = 0; i < FIELD_SIZE; i++)
    cout << "|___|";
    cout << endl;
}

void paintGameField()
{
    cout << "\tNoughts and Crosses game\n\n";

    for (int i = 0; i < FIELD_SIZE; i++)
        drawRow(i);

}

bool movePointer(int direction)
{
    bool move = false;
    switch (direction)
    {
    case UP:
        pointer_row--;
        if (pointer_row < 0)
            pointer_row = FIELD_SIZE - 1;
        move = true;
        break;
    case DOWN:
        pointer_row++;
        if (pointer_row > FIELD_SIZE - 1)
            pointer_row = 0;
        move = true;
        break;
    case LEFT:
        pointer_col--;
        if (pointer_col < 0)
            pointer_col = FIELD_SIZE - 1;
        move = true;
        break;
    case RIGHT:
        pointer_col++;
        if (pointer_col > FIELD_SIZE - 1)
            pointer_col = 0;
        move = true;
        break;
    }
    return move;
}

char pointer(int row, int col)
{
    char pointer;
    if (row == pointer_row && col == pointer_col)
        pointer = '*';
    else
        pointer = ' ';
    return pointer;
} 

bool putPlayerMark(char player_mark)
{
    bool player_turn = false;
    if (game_field[pointer_row][pointer_col] == ' ')
    {
        game_field[pointer_row][pointer_col] = player_mark;
        player_turn = true;
    }
    return player_turn;
}

void computerMove(char player_mark, char computer_mark)
{
    bool computer_move = false;
    // check if computer can win with next move
    computer_move = vitalComputerMove(computer_mark, computer_mark);

    // check if computer must do a defensive move
    if (!computer_move)
        computer_move = vitalComputerMove(player_mark, computer_mark);

    //  make non-vital computer move
    if (!computer_move)
        computer_move = checkFreeCorner(computer_mark);

    if (!computer_move)
        computer_move = checkFreeCell(computer_mark);
}

/*
   Find if computer can win with next move or make a defensive move
*/
bool vitalComputerMove(char mark, char computer_mark)
{
    int count_player_mark;
    int free_col, free_row;
    bool computer_move = false;

    //check for vital computer move in rows
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        count_player_mark = 0;
        free_col = -1;
        for (int j = 0; j < FIELD_SIZE; j++)
        {
            if (game_field[i][j] == mark)
                count_player_mark++;
            else if (game_field[i][j] == ' ')
                free_col = j;
        }
        if (count_player_mark == 2 && free_col != -1)
        {
            game_field[i][free_col] = computer_mark;
            computer_move = true;
            break;
        }
    }
    if (computer_move)
        return computer_move;

    //check for vital computer move in main diagonal
    count_player_mark = 0;
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        free_col = -1;
        free_row = -1;
        if (game_field[i][i] == mark)
            count_player_mark++;
        else if (game_field[i][i] == ' ')
            free_row = free_col = i;
    }
    if (count_player_mark == 2 && free_col != -1 && free_row != -1)
    {
        game_field[free_row][free_col] = computer_mark;
        // exit from function, no sense to check other variants
        computer_move = true;
        return computer_move;
    }

    count_player_mark = 0;
    // check for vital computer move in side diagonal
    free_col = -1;
    free_row = -1;
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        if (game_field[i][FIELD_SIZE - 1 - i] == mark)
            count_player_mark++;
        else if (game_field[i][FIELD_SIZE - 1 - i] == ' ')
        {
            free_row = i;
            free_col = FIELD_SIZE - 1 - i;
        }
    }
    if (count_player_mark == 2 && free_col != -1 && free_row != -1)
    {
        game_field[free_row][free_col] = computer_mark;
        // exit from function, no sense to check other variants
        computer_move = true;
        return computer_move;
    }

    // check for vital computer move in columns
    for (int j = 0; j < FIELD_SIZE; j++)
    {
        free_row = -1;
        count_player_mark = 0;
        for (int i = 0; i < FIELD_SIZE; i++)
        {
            if (game_field[i][j] == mark)
                count_player_mark++;
            else if (game_field[i][j] == ' ')
                free_row = i;
        }
        if (count_player_mark == 2 && free_row != -1)
        {
            game_field[free_row][j] = computer_mark;
            computer_move = true;
            break;
        }
    }
    if (computer_move)
        return computer_move;

    return computer_move;
}

bool checkFreeCorner(char computer_mark)
{
    bool computer_move = false;
    if (game_field[FIELD_SIZE / 2][FIELD_SIZE / 2] == ' ')
    {
        game_field[FIELD_SIZE / 2][FIELD_SIZE / 2] = computer_mark;
        computer_move = true;
    }
    else if (game_field[0][0] == ' ')
    {
        game_field[0][0] = computer_mark;
        computer_move = true;
    }
    else if (game_field[FIELD_SIZE - 1][FIELD_SIZE - 1] == ' ')
    {
        game_field[FIELD_SIZE - 1][FIELD_SIZE - 1] = computer_mark;
        computer_move = true;
    }
    else if (game_field[FIELD_SIZE - 1][0] == ' ')
    {
        game_field[FIELD_SIZE - 1][0] = computer_mark;
        computer_move = true;
    }
    else if (game_field[0][FIELD_SIZE - 1] == ' ')
    {
        game_field[0][FIELD_SIZE - 1] = computer_mark;
        computer_move = true;
    }

    return computer_move;
}

bool checkFreeCell(char computer_mark)
{
    bool computer_move = false;
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        for (int j = 0; j < FIELD_SIZE; j++)
        {
            if (game_field[i][j] == ' ')
            {
                game_field[i][j] = computer_mark;
                computer_move = true;
                return computer_move;
            }
        }
    }
    return computer_move;
}

int checkWinner(char computer_mark, char player_mark)
{
    int winner = -1;
    char mark;
    int count_mark;

    // check winner in row
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        count_mark = 1;
        mark = game_field[i][0];
        for (int j = 1; j < FIELD_SIZE; j++)
        {
            if (game_field[i][j] == mark)
                count_mark++;
            else
                break;
        }
        if (count_mark == WIN_LINE_LENGTH)
        {
            if (mark == computer_mark)
            {
                winner = 0;
                return winner;
            }
            else if (mark == player_mark)
            {
                winner = 1;
                return winner;
            }
        }
    }

    // check winner in main diagonal
    mark = game_field[0][0];
    count_mark = 1;
    for (int i = 1; i < FIELD_SIZE; i++)
    {
        if (game_field[i][i] == mark)
            count_mark++;
    }
    if (count_mark == WIN_LINE_LENGTH)
    {
        if (mark == computer_mark)
        {
            winner = 0;
            return winner;
        }
        else if (mark == player_mark)
        {
            winner = 1;
            return winner;
        }
    }

    // check winner in side diagonal
    mark = game_field[0][FIELD_SIZE - 1];
    count_mark = 1;
    for (int i = 1; i < FIELD_SIZE; i++)
    {
        if (game_field[i][FIELD_SIZE - 1 - i] == mark)
            count_mark++;
    }
    if (count_mark == WIN_LINE_LENGTH)
    {
        if (mark == computer_mark)
        {
            winner = 0;
            return winner;
        }
        else if (mark == player_mark)
        {
            winner = 1;
            return winner;
        }
    }

    // chek winner in columns
    for (int j = 0; j < FIELD_SIZE; j++)
    {
        count_mark = 1;
        mark = game_field[0][j];
        for (int i = 1; i < FIELD_SIZE; i++)
        {
            if (game_field[i][j] == mark)
                count_mark++;
            else
                break;
        }
        if (count_mark == WIN_LINE_LENGTH)
        {
            if (mark == computer_mark)
            {
                winner = 0;
                return winner;
            }
            else if (mark == player_mark)
            {
                winner = 1;
                return winner;
            }
        }
    }

    // check for a draw
    count_mark = 0;
    for (int i = 0; i < FIELD_SIZE; i++)
    {
        for (int j = 0; j < FIELD_SIZE; j++)
        {
            if (game_field[i][j] != ' ')
                count_mark++;
            else
                break;
        }
    }
    if (count_mark == FIELD_SIZE * FIELD_SIZE)
        winner = 2;

    return winner;
}
Подякували: Master_Sergius, Yola2

11

Re: Рух символа стрілками в консолі

Непогано!

Єдине зауваження — програма надто різко реагує на натиски стрілок. В getKeyPressed() бажано зробити невелику часову затримку, щоб курсор не перескакував через клітинку від короткого натискання.