Witajcie przyjaciele i przyszli koledzy! Całkiem niedawno zdawałem egzamin na udział w prawdziwym projekcie, zdałem go, ale tak się złożyło, że z powodów osobistych nie mogłem wziąć udziału w samym RP. Po tak ciekawych zadaniach jak test RP, zwykłe zadania z kursu stały się mniej atrakcyjną rozrywką, zwłaszcza że większość z nich już rozwiązałem. Dlatego, aby mój talent nie zmarnował się na dalszą naukę, zdecydowałem się stworzyć grę internetową dla wielu graczy. Linki do innych gier:
Kółko i krzyżyk wydawało mi się najprostszą grą, więc zdecydowałem się podzielić zadanie na kilka podzadań:
Pierwszy algorytm sprawdza całe pole
- Aplikacja konsolowa do testowania logiki gry
- Tryb wieloosobowy
- Dołączanie bazy danych graczy do aplikacji konsolowej
- Tworzenie projektu front-endu, pisanie szablonów stron, interfejsu gry
- Kładąc wszystko razem
- pole
- dwóch graczy na zmianę, jeden stawia krzyżyk, drugi zero. To proste.
String[][] strings = {{"O", "O", "_"},
{"_", "X", "O"},
{"X", "X", "X"},
for (String [] ss : strings){
for (String s : ss) System.out.print(s + " ");
System.out.println(); //для перевода строки
}
na ekranie wyświetli się:
O O _
_ X O
X X X
Ale oprócz wyświetlania mamy także porównanie wartości i tutaj opcje są już możliwe. Można porównywać ciągi znaków, można utworzyć specjalną klasę wyliczeniową ( enum
), ale ja wolę porównywać liczby i zamieniać je na „X” i „O” tylko wtedy, gdy są wyświetlane na ekranie. Niech to będzie na przykład 1 - „X”, 2 - „O”, 0 - „_”. Jak więc sprawdzić, czy w polu występuje potrójne dopasowanie X lub O?
Pierwszy algorytm sprawdza całe pole
int[][] canvas = {{00, 01, 02},
{10, 11, 12},
{20, 21, 22}}
Zwycięskie kombinacje:
00-01-02, 10-11-12, 20-21-22, 00-10-20, 01-11-21, 02-12-22, 00-11-22, 20-11-02 — всего 8.
Sprawdzam przez porównanie liczb, ale okazuje się, że za każdym razem trzeba sprawdzić CAŁE pole, wszystkie 8 kombinacji. Oczywiście to niewiele, nie jest to szukanie liczb Armstronga z zakresu od 0 do 1 miliarda, obliczeń jest tu niewiele więcej niż żadnych, ale i tak chcesz czegoś bardziej optymalnego niż sprawdzanie całego pola. Drugi pomysł, który mi przyszedł do głowy, to sprawdzić tylko komórkę, która została zaznaczona w poprzednim ruchu, dzięki czemu nadal możemy wyłonić zwycięzcę, ponieważ będziemy wiedzieć, kto wykonał ten ruch. Zatem zamiast wszystkich 8 kombinacji otrzymujemy tylko 2, 3 lub 4 kombinacje, w zależności od komórki, patrz obrazek: Teraz musimy dowiedzieć się, jak określić, która kombinacja ma zostać uruchomiona? W tym miejscu zdałem sobie sprawę, że używanie tablicy dwuwymiarowej nie jest zbyt wygodne. Postanowiłem rozważyć inne opcje. Na początku wpadłem na pomysł, żeby pole można zapisać w postaci dziewięciocyfrowej liczby, np. to samo pole, które wyświetliliśmy na ekranie, można zapisać tak: 220012111, wyjaśnię na palcach, co to jest to... Kod jest taki sam, 1 - „X”, 2 - „O” , 0 – „ „, co oznacza 220012111 = „OO__XOXX”, lub jeśli po co trzeciej cyfrze wstawisz podział wiersza i dodasz spacje dla przejrzystość:
О О _
_ Х О
Х Х Х
tutaj znowu, wygodne do przechowywania, wynaleziono urządzenie do wyświetlania, ale niewygodne do porównania! Rozwiązanie znaleziono, gdy ponumerowałem komórki 1-9, pomyślałem, bo w programowaniu odliczanie zaczyna się od 0 i numeruje je jak na obrazku.Nie zauważyłeś jakichś osobliwości? jeśli spojrzysz na powyższy obrazek, stanie się jasne, że rozwiązania z 2 kombinacjami mają nieparzysty numer seryjny, 4 kombinacje mają numer seryjny 4, 3 kombinacje mają resztę. Doszedłem więc do wniosku, że trzeba zachować pole gry w postaci regularnej tablicy liczb: prosta iteracja między liczbami, możliwość porównania zgodnie z wybranym algorytmem, proste wyjście na ekran. Jeśli chodzi o sam algorytm porównania. Przejdźmy do porządku: we wszystkich opcjach sprawdzany jest wiersz i kolumna, sprawdzamy tylko je. jeśli wyszukiwanie nie daje rezultatu, sprawdzamy numer komórki pod kątem parzystym/nieparzystym, jeśli jest nieparzysty, to wracamy do gry, nie ma sensu dalej sprawdzać, jeśli jest parzysty, sprawdzamy, czy ta komórka leży na lewej przekątnej, liczby tej przekątnej przy dzieleniu przez 4 mają resztę 0. Jeśli leży, sprawdzamy dopasowania, jeśli nie znaleziono dopasowań, sprawdzamy cyfrę 4, jeśli nie, wracamy do gry, jeśli tak, idziemy dalej przez kod i zwracamy wynik sprawdzenia ostatniej przekątnej. Prawdopodobnie dla nieprzygotowanej osoby jest to trudne do zrozumienia po przeczytaniu powyższego zestawu liter, ale ktoś może powiedzieć, że w samym kodzie jest dużo liter, co może być prostsze, chętnie to omówię. Wyjaśniliśmy pole, teraz mamy do czynienia z dwoma użytkownikami, którzy na zmianę i każdy z nich ma swój własny znak, X lub O. Na pierwszym etapie nie mamy żadnej funkcjonalności dla wielu użytkowników, więc najłatwiejszym sposobem byłoby użycie ikon jedna po drugiej. X zawsze wykonuje pierwszy ruch, O zawsze wykonuje drugi, potem znowu X i tak dalej. Aż prosi się o sprawdzenie ( prawda/fałsz ), a jeśli prawda – to aktualny gracz to X, jeśli fałsz – to O i na początku każdego ruchu flag=!flaga Pozostaje jakoś otrzymać sygnał od graczy o którym wybrana przez nich komórka. Tutaj będziemy potrzebować naszych niezapomnianych BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Graczy, wprowadzimy numery komórek do konsoli, a naciśnięcie Enter spowoduje wykonanie ruchu. Komórka odpowiadająca wprowadzonej liczbie zmieni swoją wartość z 0 na 1 lub 2 w zależności od aktualnego stanu checkboxa, co zostało omówione w powyższym akapicie. W tym miejscu ważne jest, aby sprawdzić poprawność danych wejściowych, aby nikt nie mógł zmienić X na O, gdy komórka jest już wypełniona :) Co gracz może wpisać w konsoli?
- pusta linia
- litery, znaki interpunkcyjne, nawiasy... jednym słowem, niecyfry
- nieprawidłowe liczby - ujemne lub poza rozmiarem tablicy, zajęte komórki.
Integer.parseInt("2");
Zgłasza wyjątek NumberFormatException
, jeśli nie może pobrać cyfry z danego ciągu. Możemy zapewnić ochronę od pierwszych dwóch punktów, przechwytując ten wyjątek. W punkcie trzecim stworzyłbym inną metodę sprawdzającą wpisaną wartość, jednak najrozsądniej byłoby przenieść żądanie stringa do osobnej metody, w której zostanie przeprowadzona walidacja, a zwróci ona tylko liczbę. Podsumowując, stworzyliśmy pole, zrobiliśmy metodę wyświetlającą je, stworzyliśmy metodę sprawdzającą „czy ten gracz wygrał o godzinę?” i zwalidowaliśmy wprowadzone liczby. Niewiele pozostało do zrobienia, sprawdź remis - osobna metoda, która przegląda tablicę, szuka 0 i wyświetla wyniki gry. To wszystko, kod jest gotowy, gra okazała się mała, tylko jedna klasa, więc twardzi kopiujący-wklejający mogą bez zrozumienia po prostu skopiować wszystko do swojego projektu i uruchomić go samodzielnie, sam taki byłem, ale teraz staram się tego nie robić i nikomu tego nie polecam :) Życzę wszystkim powodzenia w nauce JAVA! ps reszta punktów - multi i baza danych dojdą później, materiał już zacząłem studiować :)
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class GameField {
static int [] canvas = {0,0,0,
0,0,0,
0,0,0};
//012, 345, 678, 036, 147, 258, 048, 246
public static void main(String[] args){
boolean b;
boolean isCurrentX = false;
do {
isCurrentX = !isCurrentX;
drawCanvas();
System.out.println("mark " + (isCurrentX ? "X" : "O"));
int n = getNumber();
canvas[n] = isCurrentX ? 1 : 2;
b = !isGameOver(n);
if (isDraw()){
System.out.println("Draw");
return;
}
} while (b);
drawCanvas();
System.out.println();
System.out.println("The winner is " + (isCurrentX ? "X" : "O") + "!");
}
static int getNumber(){
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true){
try {
int n = Integer.parseInt(reader.readLine());
if (n >= 0 && n < canvas.length && canvas[n]==0){
return n;
}
System.out.println("Choose free cell and enter its number");
} catch (NumberFormatException e) {
System.out.println("Please enter the number");
} catch (IOException e) {
}
}
}
static boolean isGameOver(int n){
// 0 1 2
// 3 4 5
// 6 7 8
//поиск совпадений по горизонтали
int row = n-n%3; //номер строки - проверяем только её
if (canvas[row]==canvas[row+1] &&
canvas[row]==canvas[row+2]) return true;
//поиск совпадений по вертикали
int column = n%3; //номер столбца - проверяем только его
if (canvas[column]==canvas[column+3])
if (canvas[column]==canvas[column+6]) return true;
//мы здесь, значит, первый поиск не положительного результата
//если oznaczający n находится на одной из граней - возвращаем false
if (n%2!=0) return false;
//проверяем принадлежит ли к левой диагонали oznaczający
if (n%4==0){
//проверяем есть ли совпадения на левой диагонали
if (canvas[0] == canvas[4] &&
canvas[0] == canvas[8]) return true;
if (n!=4) return false;
}
return canvas[2] == canvas[4] &&
canvas[2] == canvas[6];
}
static void drawCanvas(){
System.out.println(" | | ");
for (int i = 0; i < canvas.length; i++) {
if (i!=0){
if (i%3==0) {
System.out.println();
System.out.println("_____|_____|_____");
System.out.println(" | | ");
}
else
System.out.print("|");
}
if (canvas[i]==0) System.out.print(" " + i + " ");
if (canvas[i]==1) System.out.print(" X ");
if (canvas[i]==2) System.out.print(" O ");
}
System.out.println();
System.out.println(" | | ");
}
public static boolean isDraw() {
for (int n : canvas) if (n==0) return false;
return true;
}
}
GO TO FULL VERSION