WinForms - obsługa zmiany grafiki

0

dopiero uczę się programować w c#/winforms i piszę prostą gierkę planszową dla dwóch graczy która ma działać w następujący sposób:

  • po kliknięciu przycisku ma następować trzykrotny rzut kością sześcienną (czyli po prostu losowanie trzech liczb)
  • po rzucie następuje takie coś że dokonujemy operacji matematycznej gdzie pierwszy i trzeci rzut to operatory a drugi to operator (+/- w zależności od tego czy wynik rzutu jest parzysty/nieparzysty) i ruszamy pionek o liczbę pól równą wynikowi operacji (również do tyłu), czyli np. pierwszy rzut = 2, drugi = 4, trzeci = 1, 2+1=3, idziemy do przodu o trzy pola
  • są pola specjalne Numer i p/np (pierwsze mają na sobie cyfrę, jak na nie staniemy to rzucamy kością i w zaleznosci od parzystości/nieparzystosci idziemy w przod/w tyl, na drugich rzucamy koscia i w zaleznosci od parzystosci/nieparzystosci idziemy w przod/w tyl o dana liczbe pol)
  • wygrywa ten kto pierwszy stanie na ostatnim polu

napisałem już klasę która implementuje całą logikę gry i pozostało tylko zintegrowac to z GUI, problem polega na tym że nie wiem gdzie zaimplementować i wywołać metody odpowiedzialne za edycję grafiki (przesuwanie pionkow, edycja labeli) tak żeby mogły one działać poprawnie - w klasie odpowiedzialnej za grafikę czy za logikę gry? w którym miejscu powinienem to zrobić? jeśli w klasie gry,jak mam dodać obiekty pictureBox do klasy odpowiedzialnej za logikę, tak żebym mógł się do nich odwoływać?

klasa odpowiedzialna za logikę:

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static projekt.graForm;

namespace projekt
{
    public class PlusIMinus     //klasa przechowujace wszystkie informacje i metody zwiazane z gra
    {
        public enum FieldType   //typy pola: normalne, numerek czy parzyste/nieparzyste
        {
            Normal,     //normalne
            Num,        //numerek
            EvenOdd     //parzyste/nieparzyste
        }

        public class Field  //klasa reprezentujaca arbitralne pole na planszy
        {
            public FieldType Type { get; set; }     //typ pola
            public string Color { get; set; }       //kolor pola
            public int Id { get; set; }    //id pola
            public int Number { get; set; }    //numer przypisany do specjalnego pola reprezentujacy przesuniecia
            public PictureBox Picture;  //wyglad pola
        }

        private int currentPlayer;   //obecny gracz, 0 - gracz czerwony, 1 - gracz niebieski
        private int redPlayerPosition;  //pozycja gracza czerwonego
        private int bluePlayerPosition; //pozycja gracza niebieskiego
        private Random random;          //generator liczb losowych
        private List<Field> gameBoard;  //lista pol

        /* metody bedace getterami/setterami do pol klasy PlusIMinus */
        public int RedPlayerPosition
        {
            get; set;
        }

        public int BluePlayerPosition
        {
            get; set;
        }

        public int CurrentPlayer
        {
            get; set;
        }

        public List<Field> GameBoard
        {
            get; set;
        }

        /*koniec getterow/setterow*/

        private void InitGame()     //inicjalizacja gry
        {
            currentPlayer = (random.Next(1, 7) % 2 == 0) ? 0 : 1; //poczatkowy rzut koscia, jak nieparzysty - zaczyna niebieski
            redPlayerPosition = 0;
            bluePlayerPosition = 0;
        }

        private void InitBoard()    //inicjalizacja planszy
        {
            for (int i = 1; i <= 58; i++)    //generacja planszy 58 pol
            {
                FieldType fieldType;
                int id = i;
                int number = 0;

                if (id == 2 && id == 8 && id == 23 && id == 29 && id == 37 && id == 47)     //pola typu numerek
                {
                    fieldType = FieldType.Num;

                    if (id == 2) number = 1;
                    else if (id == 8) number = 2;
                    else if (id == 23) number = 1;
                    else if (id == 29) number = 5;
                    else if (id == 37) number = 5;
                    else if (id == 47) number = 3;
                }
                else if (id == 12 && id == 17 && id == 21 && id == 30 && id == 52)   //pola typu p/np
                {
                    fieldType = FieldType.EvenOdd;
                }
                else fieldType = FieldType.Normal;

                string color = "White"; //domyslny kolor
                if (fieldType == FieldType.Num)     //kolor pola typu numerek
                {
                    color = "Yellow";
                }
                else if (fieldType == FieldType.EvenOdd)    //kolor pola typu p/np
                {
                    color = "Green";
                }
                GameBoard.Add(new Field { Type = fieldType, Color = color, Id = id, Number = number });  //dodanie pola do planszy

                GameBoard[0].Picture = graForm.pictureBox0; //tu jest problem - nie moge przy inicjalizacji przypisac pictureboxa symbolizujacego pole do atrybutu Picture, zatem nie moge edytowac obrazkow
            }
        }



        public void Play()     //glowna funkcja gry
        {

            while (!CheckWin())     //glowna petla gry ktora ma trwac dopoki ktos nie wygra
            {
                /*podstawowy ruch*/
                if (graForm.button1WasClicked)      //wykrycie wcisniecia przycisku rzutu koscia
                {
                    int operand1, operatorRoll, operand2;  //operatory i operand
                    RollDice(out operand1, out operatorRoll, out operand2);    //trzykrotny rzut koscia
                    int result = Operation(operand1, operatorRoll, operand2);   //wykonanie operacji


                    MovePlayer(result);     //ruch gracza
                    SwitchPlayer();     //zamiana graczy
                }
            }

            /*koniec gry*/
            string winner;
            winner = GetWinningPlayer();

        }

        private int Operation(int operand1, int operatorRoll, int operand2)     //operacja po rzucie koscia
        {
            if (operatorRoll % 2 == 0)  //parzysty wynik drugiego rzutu - suma
                return operand1 + operand2;
            else return operand1 - operand2;    //nieparzysty wynik - roznica

        }

        private int RollDiceForSpecialField()   //pojedynczy rzut koscia po stanieciu na specjalne pole
        {
            return random.Next(1, 7);
        }

        public void RollDice(out int operand1, out int operatorRoll, out int operand2)  //trzykrotny rzut koscia operand1 - operator - operand2 (przekazujemy przez out (referencje) bo chcemy zmienic)
        {
            operand1 = random.Next(1, 7);
            operatorRoll = random.Next(1, 7);
            operand2 = random.Next(1, 7);
        }

        public void MovePlayer(int result)  //ruch obecnego gracza o ilosc pol rownej wynikowi operacji
        {
            if (currentPlayer == 0) //czerwony
            {
                redPlayerPosition += result;
                if (redPlayerPosition < 0) redPlayerPosition = 0;   //kontrola bledu
            }
            else    //niebieski
            {
                bluePlayerPosition += result;
                if (bluePlayerPosition < 0) bluePlayerPosition = 0;     //kontrola bledu
            }
            CheckSpecialField();    //sprawdzenie czy nie wyladowalismy na specjalnym polu
            ChangePicture();        //zmiana obrazka - czy poprawnie?
            
        }

        private void ChangePicture()    //zmiana wygladu pol na ktore staje gracz
        {
            //jak to zaimplementowac?
            throw new NotImplementedException();
        }

        private void CheckSpecialField()    //sprawdzenie rodzaju pola i wywolanie odpowiedniej funkcji
        {
            int currentPlayerPosition = (currentPlayer == 0) ? redPlayerPosition : bluePlayerPosition;  //pozycja gracza ktory stanal na pole i ktory to gracz
            Field currentField = gameBoard[currentPlayerPosition];  //pole na ktorym stanal gracz

            if (currentField.Type == FieldType.Num)     //jesli stanelismy na numerku
            {
                int diceResult = RollDiceForSpecialField();
                if (diceResult % 2 == 0)    //ruch do przodu jesli wynik parzysty
                {
                    MovePlayer(currentField.Number);
                }
                else MovePlayer(-(currentField.Number));    //ruch do tylu jesli wynik nieparzysty
            }
            else if (currentField.Type == FieldType.EvenOdd)    //jesli stanelismy na p/np
            {
                int diceResult = RollDiceForSpecialField();
                int moveBy;
                if (diceResult % 2 == 0)    //ruch do przodu jesli wynik parzysty
                {
                    moveBy = diceResult;
                }
                else moveBy = -diceResult;  //ruch do tylu jesli wynik nieparzysty
                MovePlayer(moveBy);
            }
        }

        public void SwitchPlayer()  //zamiana graczy
        {
            currentPlayer = (currentPlayer == 0) ? 1 : 0;
        }

        public bool CheckWin()  //sprawdzenie warunku wygranej
        {
            return redPlayerPosition >= 58 || bluePlayerPosition >= 58;
        }

        public string GetWinningPlayer()    //string zwyciezcy
        {
            return (currentPlayer == 0) ? "Red" : "Blue";
        }

        public PlusIMinus()     //konstruktor
        {
            random = new Random();
            InitGame();
        }
    }
}


klasa odpowiedzialna za GUI:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static projekt.PlusIMinus;

namespace projekt
{

    public partial class graForm : Form
    {
        private PlusIMinus PIM;

        public graForm()
        {
            InitializeComponent();
            PIM = new PlusIMinus();     //stworzenie obiektu przechowujacego wszystkie informacje o grze i wywolanie konstruktora
            PIM.Play();
            /*czy tu powinna byc metoda edytujaca grafike?*/
        }

        public static bool button1WasClicked = false;     //detekcja klikniecia przycisku
        private void button1_Click(object sender, EventArgs e)      //klikniecie przycisku odpowiadajacego za rzut koscia
        {
            button1WasClicked = true;

        }

    }


}
1

Od powiadajac na szybko, moze zastosować wzorzec mvvm. Modelm jest twoja logika, View jest bedzie kod potrafiący narysować plansze, rzuty kostką itd. ogólie forma. ViewModelem bedzie klasa, która przekierowuje logikę między jednym a drugim. Np. naciśnięto rzut kostką we view zrób to w modelu, lub gdy skończyła się tura w modelu odśwież UI.

Informacje z Modelu, do viewModelu mozesz przekazywać eventami.
Z View do viewModelu z reguły najprosciej jest Wywoływać metody i się nie wygłupiać.

Innym dobrym patentem jest. Napisanie metody która odczytuje z modelu do viewModelu wszystkie dane na pałe. I odświeżanie wszystkiego co interakcje.

0

Usunęło mi post w trakcie pisania...

Musisz zmienić podejść i usunąć BOSKĄ PĘTLE WHILE, winformsy są frameworkiem jednowątkowym i się zwyczajnie zawieszą przy obecnym podejściu. Kopiąc się z koniem można zmusić takie rozwiązanie do działania, ale zamiast tego lepiej jest jest korzystać z frameworka tak jak to wymyślono czyli, reagować na eventy.
W tym celu suwasz bool'a graForm.button1WasClickeda, logike przepisujesz tak by bolejne kroki odbywały sie na wydarzenie następny rzut kostką.

Po stronie View i ViewModelu,(bo ten pattern proponowałem w pierwszym poście) wyglądało by to tak:

    private void button1_Click(object sender, EventArgs e)      //klikniecie przycisku odpowiadajacego za rzut koscia
    {
        this.ViewModel.UserClicked_DiceRoll();
    }        

    ... view model ...
    
    private void UserClicked_DiceRoll()      
    {
        this.logic.NextRoll();
        this.ReadStateFromModel(this.logic);
        
    }

Ponieważ masz bardzo proste dane oraz nie znam dobrej metody bindowania danych dla winforms, innej niż ręcznie, proponował co każde odświezenie odczytywać stan od zera i wgrywać go do UI. Na marginesie, jeśli nie ma problemu z wydajnością to, jest to domyślna metoda tworzenia 80% UI. W takim wypadku ReadStateFromModel odczyta wszystkie dane z modelu, obecnego gracza, pozycje, wynik rzutu itd. i zapisze je do view modelu. Na pierwszy rzut oka takie rozwiązanie jest redundatne- takie masło maślane- viewModel.pozycja = model.pozycja. Sekret polega na tym by nie robić mapowania 1:1, tylko tak z głową. Np. Ilość wyrzuconych oczek, mapujesz na scieżke do obrazka z kostką, aktualnego gracza na kolor tła, pozycje z jednostek wględnych index:1 na pozycje na planszy np. 234x, 234y.

Na koncu metody, ReadStateFromModel gdy już przepiszesz wszystkie dane z modelu do viewModelu, wysyłasz event że stan sie zmienił. Forma bedzie tego eventu na słuchiwać i gdy się pojawi: wpisze nowe wartości do odpowiednich kontrolek.. ścieżke jako źródło do obrazków, kolor tła gracza jako gradient żeby było ładnie, a pozycje płynnie przesunie animacją i być moze poinformuje view model ze juz wszystko gotowe i czeka na ruch. To ostatnie z reguły się pomija, a jeśli się nie pomija, to implementuje się "maszyne stanów".

Mając taki model danych, w warstwie modelu(gry) skupiasz się na tymby jak najlepiej przetworzyć dane, w warstwie view(forma) skupiasz się tylko i wyłacznie na tym by wszystko wygladało jak najlepiej i w ogóle działało(jakieś 80% kodu to będzie wpisany na sztywno wygląd podelementów oraz walka z frameworkiem) A we view modelu, piszesz komunikacje oraz do dajesz wszystkie brudne haki, które trzeba gdzieś napisac.

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