Snake w tablicy

0

Witam. Ostatnio wziąłem się za zaimplementowanie Snake'a w C++. Piszę temat, ze względu na to, że wszelkie inne kody znalezione w necie czy tutaj są dla mnie jeszcze nie zrozumiałe, tzn. są funkcje, których jeszcze nie poznałem. Pragnę dodać, że Snake'a tworzę w tablicy dwuwymiarowej - babka na ćw. wymaga od nas użycia w programie tablic, pętli oraz warunków. Na obecną chwilę zmagam się z napisaniem kodu sterowania wężem. Sposób w jakim ja myślę jednak nie podoba się Visual'owi - program działa, ale sposób sterowanie już nie:

 
#include "stdafx.h"
#include <iostream>
#include <conio.h>

using namespace std;

int main()
{
	char p;
	char t;
	char tab[20][70];																		// Zadeklarowanie tablicy jako pola do gry.
	char k;
	int a = 10;
	int b = 34;
	tab[a][b] = '^';

	cout << "SNAKE - THE GAME" << '\n' << '\n' << "OTWORZ MENU START, WYBIERAJAC 'S' I AKCEPTUJAC." << '\n';		// Prezentacja programu oraz proste menu do uruchomienia gry.

	do																					//  Pętla, która będzie prosiła o spełnienie postawionego warunku.
	{
		cin >> t;
		cout << "WYBIERZ I ZAAKCEPTUJ 'S' BY ROZPOCZAC!" << '\n';
	}

	while (t != 'S');

	cout << "CZY CHCESZ ROZPOCZAC GRE? TAK - 1, WYJSCIE - 2" << '\n';					// Menu gry - wejście lub wyjście z gry.
	cin >> p;
	cout << '\n' << '\n' << '\n' << '\n' << '\n';

	if (p == '1')																		// Pierwszy warunek - warunek akceptacji rozpoczęcia gry.
	{
		for (int a = 0; a < 20; a++)													// Pętle tworzące ramkę w tablicy, wyznaczająca pole do gry. 
		{
			for (int b = 0; b < 1; b++)
			{
				tab[a][b] = '*';
			}
		}

		for (int a = 0; a < 1; a++)
		{
			for (int b = 0; b < 70; b++)
			{
				tab[a][b] = '*';
			}
		}

		for (int a = 19; a < 20; a++)
		{
			for (int b = 1; b < 70; b++)
			{
				tab[a][b] = '*';
			}
		}

		for (int a = 1; a < 20; a++)
		{
			for (int b = 69; b < 70; b++)
			{
				tab[a][b] = '*';
			}
		}

		for (int a = 1; a < 19; a++)
		{
			for (int b = 1; b < 69; b++)
			{
				tab[a][b] = ' ';
			}
		}
	
		for (int a = 0; a < 20; a++)
		{
			for (int b = 0; b < 70; b++)
			{
				cout << tab[a][b];
			}
			cout << '\n';
		}

		do
		{
			cin >> k;

			if (k == 'w')
			{
				a++;
				tab[a][b];
			}
			
			else if (k == 's')
			{
				a--;
				tab[a][b];				
			}

			else if (k == 'd')
			{
				b++;
				tab[a][b];							
			}

			else if(k == 'a')
			{
				b--;
				tab[a][b];				
			}
					
		} while (a != 0);


      }

	cout << '\n' << '\n' << '\n';
	return 0;
}

Nie wiem jak mógłbym przemieszczać "głowę" węża w tej tablicy. Próbowałem nie wstawiać spacji w środku ramki (usuwając pętle), ale wyskakują mi dziwne symbole, a i tak + się nie przemieści. A i jak mam tą pętlę z spacją to + się nie pojawia w ogóle. Aktualnie stoję i nie wiem jak ruszyć z tym dalej.

1

Ja tam w C++ nie piszę, ale sam z ciekawości implementowałem jakiś czas temu konsolowego węża, tutaj opis - SnakeASCII; Co prawda gra pisana we Free Pascalu, ale zrzuty są, więc można zobaczyć efekt;

O ile dane węża były opakowane w klasę, to pojedyncze moduły zapisane były w zwykłej, jednowymiarowej tablicy; Każdy moduł to może być zwykła struktura - współrzędne X i Y na ekranie oraz typ modułu (głowa, ciało, ogon itp.); Dzięki temu łatwo jest sprawdzić ewentualną kolizję z ciałem węża lub ramką planszy;

Przy przerysowywaniu węża w każdej kolejnej "klatce" wystarczy zamazać znakiem spacji ogon, zmienić moduł karku węża (moduł zaraz za głową), a głowę namalować w nowym miejscu; Czyli na każdą klatkę gry malujesz tylko trzy znaki, a nie cały ekran (dzięki temu gra nie miga);

Jeśli w tym kierunku pójdziesz to zadeklaruj sobie dwie tablice - jedną jednowymiarową dla danych węża i jedną dwuwymiarową z ramką planszy; Na podstawie wciśniętego klawisza odczytujesz współrzędne głowy i sprawdzasz, czy można przesunąć głowę w nowy kierunek; Jeśli tak to przerysowujesz wymienione znaki i przepisujesz pola tablicy, aby zaktualizować współrzędne modułów węża;

Natomiast głowa węża powinna być pierwszym elementem tej jednowymiarowej tablicy; Dzięki temu po zjedzeniu jabłka(czy jak to inaczej nazwać można) dodajesz nowy element do tablicy, nie ruszając pozostałych;

IMHO taki sposób jest dość prosty w implementacji i nie wymaga różnych dziwnych kombinacji.

0

Postaram się pomóc, kolega wyżej ma rację we wszystkim prócz wyświetlania. Niestety programując w konsoli tak, czy siak potrzebujesz za każdym obiegiem pętli razem malować całą planszę.

A co do Twojego kodu to:

  1. nie odświeżasz pola gry. Powinieneś dodać główną pętlę, która co wciśnięcie klawisza sterowania będzie wyświetlać w konsoli nowy stan pola gry z nowym położeniem węża. (mało płynne, ale nie wiem jak bez dodatkowych bibliotek zrobić to lepiej)
  2. Rysowanie ramki to walnąłbym w jakąś funkcję, poza tym zamiast robić np.
for (int a = 0; a < 1; a++)
        {
            for (int b = 0; b < 70; b++)
            {
                tab[a][b] = '*';
            }
        }

zrób

for(int b = 0; b < 70; b++)
    tab[0][b] = '*';
  1. sprawdzaj po kliknięciu klawisza sterowania czy głowa węża nie uderza w samego węża lub w ścianę. Możesz to robić na co najmniej 2 sposoby:
    trzymać węża w oddzielnej tablicy i porównywać nową pozycję głowy z każdym elementem węża i dodatkowo porównywać pozycję głowy z pozycją ścian.
    Możesz też trzymać wszystko w jednej tablicy dwuwymiarowej jak ta Twoja i oznaczać węża jakimś symbolem np. O, wtedy porównujesz pozycję głowy z zawartością tablicy w tym miejscu, jeśli nie ma tam nic to OK, jeśli jest wąż bądź ściana to kolizja, GAME OVER.
0

Mógłbym jeszcze poprosić o tę funkcję odświeżania? Dalej powinienem już sobie poradzić. Mówię, jestem dopiero nowicjuszem i wiele przede mną, więc jeszcze nie wszystko ogarniam w C++.

1
SecurityMan napisał(a)

Postaram się pomóc, kolega wyżej ma rację we wszystkim prócz wyświetlania. Niestety programując w konsoli tak, czy siak potrzebujesz za każdym obiegiem pętli razem malować całą planszę.

W tym także się nie mylę i pod żadnym pozorem nie trzeba czyścić całej konsoli; Jest to bardzo nadmiarowe i jeśli istnieje lepsze rozwiązanie to trzeba z niego skorzystać; Wystarczy trochę poszukać w Google i funkcja przenosząca kursor w zadane współrzędne w konsoli gotowa:

#include <windows.h>

void gotoxy(int x, int y)
{
    COORD p = {x, y};
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), p);
}

Źródło: http://www.cplusplus.com/forum/general/41709/

Mając taką funkcję można używać jej wszędzie tam, gdzie trzeba zmienić czy zamazać tylko jeden znak; Powyższy kod opieram na wynikach znalezionych przez wyszukiwarkę (w razie czego jest też odpowiednik dla uniksów); Tak jak wspomniałem wyżej - nie programuję na co dzień w C++ i nie jestem "na czasie" z tym językiem;

PS: W Google można znaleźć jeszcze dwie przydatne rzeczy, jeśli o tworzenie gierek w konsoli chodzi - kod zmieniający kolor tekstu oraz zmieniający kolor tła tekstu; Jeśli w C++ nie ma już zaimplementowanych w stdlibie takich funkcji to też można sobie ładnie opakować wywołania instrukcji WinAPI i używać do woli.

0

No dzięki tej metodzie poszło wszystko gładko. Jednak zanim przejdę to warunku tworzenia ogona węża, połykania jedzenia oraz kolizji z ogonem, to mam jeszcze 2 problemy, których nie umiem rozwiązać - samo sterowanie działa, ale potrzebuje funkcji, która będzie wykonywała daną czynność po wciśnięciu danego klawisza. Czytałem o getch(), ale funkcja nie reaguje jak ją wprowadziłem. Znacie jakąś inną, podobną funkcję? I nie wiem gdzie, ale zawsze jak wyświetla głowę węża, to obok niej (znak x) pojawia się a. Szukam, szukam i nie umiem znaleźć skąd się to bierze.

#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <windows.h>

using namespace std;

char gotoxy(int a, int b)
{
	COORD p = { a, b };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), p);
	return 0;
}

int main()
{
	char p;
	char t;
	char h;
	int a = 34;
	int b = 10;

	cout << "SNAKE - THE GAME" << '\n' << '\n' << "OTWORZ MENU START, WYBIERAJAC 'S' I AKCEPTUJAC." << '\n';		// Prezentacja programu oraz proste menu do uruchomienia gry.

	do																					//  Pętla, która będzie prosiła o spełnienie postawionego warunku.
	{
		cin >> t;
		cout << "WYBIERZ I ZAAKCEPTUJ 'S' BY ROZPOCZAC!" << '\n';
	}

	while (t != 'S');

	cout << "CZY CHCESZ ROZPOCZAC GRE? TAK - 1, WYJSCIE - 2" << '\n';					// Menu gry - wejście lub wyjście z gry.
	cin >> p;
	cout << '\n' << '\n' << '\n' << '\n' << '\n';
	system("cls");																		// Wyczyszczenie menu gry 

	if (p == '1')																		// Pierwszy warunek - warunek akceptacji rozpoczęcia gry.
	{
	
		do
		{

		for (int b = 0; b < 20; b++)                                                    // Pętle tworzące ramkę w tablicy, wyznaczająca pole do gry. 
		{
			for (int a = 0; a < 1; a++)
			{
				gotoxy(a, b);
				cout << '*';
			}
		}

		for (int b = 0; b < 1; b++)
		{
			for (int a = 0; a < 70; a++)
			{
				gotoxy(a, b);
				cout << '*';
			}
		}

		for (int b = 19; b < 20; b++)
		{
			for (int a = 1; a < 70; a++)
			{
				gotoxy(a, b);
				cout << '*';
			}
		}

		for (int b = 0; b < 20; b++)
		{
			for (int a = 69; a < 70; a++)
			{
				gotoxy(a, b);
				cout << '*';
				
			}
		}
		
		cout << gotoxy(a, b) << 'x';
		cin >> h;
			
			if (h == 'w')
			{

				b--;				

			}

			if (h == 's')
			{
				
				b++;
			}
			
			if (h == 'a')
			{
				
				a--;
			}
			
			if (h == 'd')
			{
			
				a++;
			}

			system ("cls");

		} while (b != 0 && b != 19 && a != 0 && a != 69);

		

		cout << '\n' << '\n' << '\n';
		return 0;
	}
}
 
0

Sprawdź kbhit przed getch.

0

Nie umiem w jakiś sensowny sposób tej kombinacji zaimplementować - przypadki z google też mi niewiele pomogły.

1

@Theraselking - podałem Ci kod funkcji przesuwającej kursor i nic więcej, a Ty zmodyfikowałeś ją niepotrzebnie... Oryginalnie była void, czyli nic nie zwracająca (bo nie ma takiej potrzeby i nie do tego służy), a teraz nagle zwraca znak typu char, w dodatku zawsze null; O ile problemem nie jest niejawne rzutowanie, to niepotrzebne i nieuzasadnione dokładanie kodu owszem;

Wróć do poprzedniej postaci, ewentualnie powiększ jej funkcjonalność o malowanie znaku w podanych współrzędnych, bo to na pewno przyda się do malowania "animacji" ruchu węża:

void put_char_xy(int x, int y, char symbol)
{
    COORD point = {x, y};
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), point);
    cout << symbol;
}

A tak w ogóle to przydałby się jeszcze kod, który na samym początku ukryje tekstowy kursor, a na koniec z powrotem go pokaże; Gra, w której widać latający po ekranie kursor nie będzie dobrze wyglądać;

Co jest ważne - jeśli gracz odpali grę to powinieneś (według tego sposobu który proponuję) wyczyścić ekran konsoli, namalować ramkę planszy, a dopiero po tym przejść do pętli obsługującej sterowanie wężem; Czyli ogólnie pisząc, namalować raz to co się nie zmienia (np. ramkę planszy), a w pętli przemalowywać tylko określone moduły węża (głowę, kark i ogon); Kark możesz pominąć, jeśli załamania węża będziesz malował tym samym znakiem co proste jego odcinki;

Spróbuj w ten sposób, na spokojnie.

1

@Theraselking Po prostu to tu zostawię
https://github.com/spartanPAGE/spartan-snake-implementation/tree/master

#include <iostream>
#include <thread>
#include <chrono>
#include <string>
#include <algorithm>
using namespace std;
using namespace std::literals;

#include "console-utils.hpp"

#include "keyboard_input_handler.hpp"
#include "buffered_canvas.hpp"
#include "console_canvas_drawer.hpp"
#include "snake.hpp"
#include "snake_drawer.hpp"

auto gen_snake_body(){
    Snake::Body result;
    string text = "spartanPAGE";
    for(size_t i = 0; i < text.size(); ++i)
        result.push_back({i, 1, text[i]});
    reverse(begin(result), end(result));
    return result;
}

int main(){
    bool running = true;
    BufferedCanvas<char> canvas(70, 20);
    ConsoleCanvasDrawer console_drawer(canvas, Console::write_character_at);
    Snake snake(gen_snake_body(), Way::down, {0, 0, canvas.width(), canvas.height()});
    SnakeDrawer snake_drawer(canvas, snake);

    KeyboardInputHandler keyboard({
        {'e', [&]{ running = false; }},
        {'w', [&]{ snake.set_direction(Way::up); }},
        {'s', [&]{ snake.set_direction(Way::down); }},
        {'a', [&]{ snake.set_direction(Way::left); }},
        {'d', [&]{ snake.set_direction(Way::right); }}
    }, Console::keyboard_hit, Console::get_character);

    
    auto begin_drawing = [&]{ canvas.fill('.'); };
    auto end_drawing = [&]{ console_drawer.update(); };

    Console::cursor_visible(false);
    console_drawer.draw();
    while(running){
        keyboard.update();
        snake.move();

        begin_drawing();
        snake_drawer.draw();
        end_drawing();

        canvas.update();

        this_thread::sleep_for(50ms);
    }
}
0

Ok dzięki, zobaczę co uda mi się z tym zrobić.

0

Jak tylko powiedziałem babie, że robię tego Snake'a na współrzędnych konsoli, to od razu mi powiedziała, że mam to przerobić na tablicę. Na szczęście nie było to nic wielkiego. Zacząłem się zastanawiać nad tworzeniem kolejnych elementów węża. Przedtem zaimplementowałem owocek, który będzie się pojawiał na planszy i teraz za waszymi radami próbuje zrobić tego węża jako tablicę jednowymiarową - stworzyłem tablicę dynamiczną gdyż, ilość elementów tablicy jest oznaczona zmienną i musi się zwiększać o 1, a w zwykłej tego nie da się zrobić. Potrzebuje takiego algorytmu, który będzie zapisywał współrzędne dla elementu np. 1, a później czytał te same współrzędne dla poprzedniego elementu, np nr 2, i tak dalej, tak żeby wąż się mógł zginać. I pytanie - X zostawić, czy go jednak oznaczyć jako początek tablicy?

#include "stdafx.h"
#include <iostream>
#include <conio.h>
#include <time.h>
#include <windows.h>

using namespace std;

int main()
{
	srand(time(NULL));
	char p;
	char t;
	char tab[20][70];                                                                        // Zadeklarowanie tablicy jako pola do gry.
	int a = 10;
	int b = 34;
	int x = 1, y = 0;
	tab[a][b] = 'X';

	cout << "SNAKE - THE GAME" << '\n' << '\n' << "OTWORZ MENU START, WYBIERAJAC 'S' I AKCEPTUJAC." << '\n';        // Prezentacja programu oraz proste menu do uruchomienia gry.

	do                                                                                    //  Pętla, która będzie prosiła o spełnienie postawionego warunku.
	{
		cout << "WYBIERZ I ZAAKCEPTUJ 'S' BY ROZPOCZAC!" << '\n';
		cin >> t;

	}	while (t != 'S');

	system("cls");

	cout << "CZY CHCESZ ROZPOCZAC GRE? TAK - 1, WYJSCIE - 2" << '\n';                    // Menu gry - wejście lub wyjście z gry.
	cin >> p;
	cout << '\n' << '\n' << '\n' << '\n' << '\n';
	system("cls");
	
	if (p == '1')                                                                        // Pierwszy warunek - warunek akceptacji rozpoczęcia gry.
	{
		int c, d;
		c = rand() % 18 + 1;
		d = rand() % 68 + 1;
		
		char *tablica;
		int n = 1;
		tablica = new char[n];
		*tablica = '0';

		do
		{
			
			int h = 0;


			for (int a = 1; a < 19; a++)
			{
				for (int b = 1; b < 69; b++)
				{
					tab[a][b] = ' ';
				}
			}
		
			

			if (_kbhit())
			{
				h = _getch();

				if (h == 'w')
				{
					y = -1;
					x = 0;
				}

				if (h == 's')
				{
					y = 1;
					x = 0;
				}

				if (h == 'a')
				{
					y = 0;
					x = -1;
				}

				if (h == 'd')
				{
					y = 0;
					x = 1;
				}
			}

			a += y;
			b += x;
			tab[c][d] = 'F';
			tab[a][b] = 'X';
			tab[a - y][b - x] = *tablica;
			Sleep(80);
	
			if (tab[a][b] == tab[c][d])
			{
				tab[c][d] = ' ';
				c = rand() % 18 + 1;
				d = rand() % 68 + 1;
				
				n++;
				*tablica = '0';
				tab[a - y][b - x] = tab[a][b];
				
			}
					
			for (int a = 0; a < 20; a++)                                                    // Pętle tworzące ramkę w tablicy, wyznaczająca pole do gry. 
			{
				for (int b = 0; b < 1; b++)
				{
					tab[a][b] = '*';
				}
			}

			for (int a = 0; a < 1; a++)
			{
				for (int b = 0; b < 70; b++)
				{
					tab[a][b] = '*';
				}
			}

			for (int a = 19; a < 20; a++)
			{
				for (int b = 1; b < 70; b++)
				{
					tab[a][b] = '*';
				}
			}

			

			for (int a = 1; a < 20; a++)
			{
				for (int b = 69; b < 70; b++)
				{
					
					tab[a][b] = '*';
				}
			}

			system("cls");
			
			for (int a = 0; a < 20; a++)
			{
				for (int b = 0; b < 70; b++)
				{
					cout << tab[a][b];
				}
				cout << '\n';
			}
		
		
		} while (a != 0 && a != 19 && b != 0 && b != 69);
	}

	cout << '\n' << '\n' << '\n';
	return 0;
}
 
0

Zabierz się za ten kod od początku - odstaw to co masz (ale nie kasuj, bo się przyda) i napisz jeszcze raz wszystko od początku; Na początek zajmij się planszą i wężem, zaimplementuj to dobrze; Potem pobawisz się w jedzonko i menu;

Tutaj (tak jak w każdym programie dłuższym niż Hello World) przyda się podział kodu na funkcje; Chyba że nauczycielka zabroni Ci się wykazać i samemu rozwijać, to już trudno - wszystko walnij w main.

1 użytkowników online, w tym zalogowanych: 0, gości: 1