Asynchroniczność, problem z odpowiednim sprawdzeniem flagi

Asynchroniczność, problem z odpowiednim sprawdzeniem flagi
Norbert-2204
  • Rejestracja:9 miesięcy
  • Ostatnio:2 miesiące
  • Postów:12
0

Mam pewniej problem z listenerem i funkcjami zawartymi w niej.
Spróbuje wyjaśnić problem jak najlepiej potrafię.
Przechwytywanie.PNG
Mamy tutaj normal attack i strong attack, Bohater jest generowany dynamicznie po wybraniu jednego z nich więc cały html dotyczący postaci nie jest dodawany po załadowaniu strony.

Kopiuj
if (selectedHero) {
    const playerHtml = `
      <div class="player-block hero-img" data-hero-id="${selectedHero.id}" style="background-image: url('${selectedHero.img}')">
        <p>${selectedHero.name}</p>
        <div>
          <p class="hp-amount">HP: ${selectedHero.hp}</p>
          <p class="mp-amount">MP: ${selectedHero.mp}</p>
        </div>
        <div class="ability-buttons">
          <div class="ability-section">
            <div class="focus-hover-placement focus-placement">
             ${focusHTML}
             <p class="hidden-info">Focus your mind to regain mana MP + 25</p>
            </div>
          </div>
          <div class="ability-section skills-ability-section">
            <button class="all-buttons js-skills-button">Skills</button>
            <div class="new-skills-button hidden js-new-skills-button">${skillsHTML}</div>
          </div>
          <div class="ability-section magic-ability-section">
            <button class="all-buttons js-magic-button">Magic</button>
            <div class="new-magic-button hidden js-new-magic-button">${magicHTML}</div>
          </div>
        </div>
      </div>
    `;
    playerSection.insertAdjacentHTML("beforeend", playerHtml);
    if (currentPlayerIndex < Object.keys(playerNumber).length) {
      playerNumber[`player${currentPlayerIndex + 1}`] = selectedHero;
      currentPlayerIndex++;
    } else {
      console.log("Wszyscy gracze są już zajęci!");
    }

    const skillsButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .skills-ability-section`
    );
    const addSkillsList = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .js-new-skills-button`
    );
    const magicButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .magic-ability-section`
    );
    const addMagicList = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .js-new-magic-button`
    );
    const addfocusButton = playerSection.querySelector(
      `.player-block[data-hero-id="${selectedHero.id}"] .focus-placement`
    );

    skillsButton.addEventListener("click", () => {
      addSkillsList.classList.toggle("hidden");
      if (!addMagicList.classList.contains("hidden")) {
        addMagicList.classList.add("hidden");
      }
    });

    magicButton.addEventListener("click", () => {
      addMagicList.classList.toggle("hidden");
      if (!addSkillsList.classList.contains("hidden")) {
        addSkillsList.classList.add("hidden");
      }
    });

kod jest oczywiście częściowy
Teraz tak, klikając którykolwiek z umiejętności pojawia się wybór celu, czyli funkcja zadająca obrażenia czeka na wybranie celu.
Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack. Nie dawno miałem problem z tym że listenery się nakładały więc kliknięcie normal attack, po czym strong attack skutkowało nałożeniem się efektów co sprawiło że zadane obrażenia sumowały się z obu ataków. Postawiłem flage sprawdzającą, teraz efekty się nie nakładają za to nie wykonuję ostatnio wybranego skilla.
podam funkcje zajmujące się tym wszystkim, aktualnie pracuję tylko nad normal i strong attack więc reszty nie bierzcie pod uwagę.
To jest event listener:

Kopiuj
addSkillsList.addEventListener("click", (event) => {
      console.log("reakcja");
      if (event.target.classList.contains("warrior-normal-attack")) {
        if (currentAttack !== "warrior-normal") {
          currentAttack = "warrior-normal";
          checkAttack = "warrior-normal";
        }
        // checkAttack = "warrior-normal";
        // currentAttack = "warrior-normal";
        handleAbilityUse(event, "warrior-normal-attack");
      } else if (event.target.classList.contains("warrior-strong-attack")) {
        if (currentAttack !== "warrior-strong") {
          currentAttack = "warrior-strong";
          checkAttack = "warrior-strong";
        }
        // checkAttack = "warrior-strong";
        // currentAttack = "warrior-strong";
        handleAbilityUse(event, "warrior-strong-attack");
      } else if (event.target.classList.contains("spear-stab")) {
        checkAttack = "spear-stab";
        currentAttack = "spear-stab";
        if (currentAttack === "spear-stab") {
          handleAbilityUse(event, "spear-stab");
        }
      } else if (event.target.classList.contains("enrage")) {
        checkAttack = "enrage";
        currentAttack = "enrage";
        if (currentAttack === "enrage") {
          handleAbilityUse(event, "enrage");
        }
      } else if (event.target.classList.contains("leap")) {
        checkAttack = "leap";
        currentAttack = "leap";
        if (currentAttack === "leap") {
          handleAbilityUse(event, "leap");
        }
      } else if (event.target.classList.contains("brutal-takedown")) {
        checkAttack = "brutal-takedown";
        currentAttack = "brutal-takedown";
        if (currentAttack === "brutal-takedown") {
          handleAbilityUse(event, "brutal-takedown");
        }
      }
    });

Teraz funkcja handleAbility, zawiera tylko to co posiada Warrior więc jest ucięta

Kopiuj
async function handleAbilityUse(event, abilityName) {
      switch (abilityName) {
        //warrior
        case "warrior-normal-attack":
          createAbilityManaPlus(
            event,
            "warrior-normal-attack",
            15,
            20,
            "normal attack",
            1.7,
            null,
            10
          );
          break;
        case "warrior-strong-attack":
          createAbilityManaPlus(
            event,
            "warrior-strong-attack",
            20,
            30,
            "strong attack",
            1.7,
            null,
            5
          );
          break;
        case "spear-stab":
          createAbilitySkill(
            event,
            "spear-stab",
            30,
            45,
            "spear stab",
            8,
            1.7,
            null
          );
          break;
        case "enrage":
          createAbilitySkill(event, "enrage", 50, 70, "enrage", 15, 1.7, null);
          break;
        case "leap":
          createAbilitySkill(event, "leap", 70, 100, "leap", 22, 1.7, null);
          break;
        case "brutal-takedown":
          createAbilitySkill(
            event,
            "brutal-takedown",
            150,
            300,
            "brutal takedown",
            40,
            1.7,
            null
          );

Tutaj funkcja zajmująca się atakiem:

Kopiuj
async function createAbilityManaPlus(
      event,
      selector,
      minDamage,
      maxDamage,
      infoDisplay,
      buffRatio,
      buffEnhance,
      mp
    ) {
      if (event.target.classList.contains(selector)) {
        await selectTarget();
        console.log(checkAttack, "checkAttack w createA przed sprawdzeniem");
        console.log(
          currentAttack,
          "currentAttack w createA przed sprawdzeniem"
        );
        if (checkAttack !== currentAttack) {
          console.log(
            checkAttack,
            "check attack w createA po sprawdzeniu, ",
            currentAttack,
            "currentAttack w createA po sprawdzeniu, ",
            "atak sie zmienił, anulowanie w createAbility, "
          );
          return;
        }
        //
        if (!selectedTarget) {
          return;
        }

        let targetElement = selectMonster(selectedTarget);
        let playerDmg = calculateDmg(minDamage, maxDamage);
        if (buffRatio === null) {
        } else if (buffStats === true) {
          playerDmg = Math.round((playerDmg *= buffRatio));
          buffStats = false;
        }
        if (buffEnhance === null) {
        } else if (buffEnhance === true) {
          playerDmg = Math.round((playerDmg *= enhanceBuff));
          buffEnhance = false;
        }

        reduceHp(selectedTarget, targetElement, playerDmg);
        const playerBlock = event.target.closest(".player-block");
        const heroId = playerBlock.getAttribute("data-hero-id");
        const heroMPElement = playerBlock.querySelector(".mp-amount");
        const hero = heroes.find((hero) => hero.id === heroId);
        gainMana(mp, hero, heroMPElement);
        showInfo(playerDmg, hero, infoDisplay);

        if (checkAttack === currentAttack) {
          checkAttack = null;
        }
        hideSelectTarget();
      }
    }

Tutaj funkcja oczekująca wybranie celu:

Kopiuj
async function selectTarget() {
  return new Promise((resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget, ");
    console.log(
      currentAttack,
      "currentAttak przed sprawdzeniem w selectTarget, "
    );
    targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {
        if (checkAttack !== currentAttack) {
          console.log(
            checkAttack,
            "check attack w sprawdzeniu, ",
            currentAttack,
            "current attack w sprawdzeniu, ",
            "atak się zmienił, anulowanie ataku w selectedTarget, "
          );
          resolve(null);
          return;
        }
        selectedTarget = event.target.getAttribute("data-target");
        resolve(selectedTarget);
      });
    });
  });
}

Wyjaśnienie flagi: checkAttack i currentAttack to flagi sprawdzające żeby nie można było nałożyć efektów klikając tego samego skilla kilkukrotnie i by nie nakładać efektów klikając różne skille typu klik 3x normal attack skutkujące wykonanie listenera 3 razy bądz kliknięcie wszystkich umiejętności i wykonanie się wszystkich na raz po wybraniu celu.

Sprawdzając console.log checkAttack (hA) i currentAttack (cA) w róznych miejscach zauważyłem że np. po kliknięciu normal attack, hA i cA są prawidłowe, po wybraniu celu i wykonaniu całej logiki hA i cA są prawidłowe. Teraz po kliknięciu normal attack hA i cA są prawidłowe (warrior-normal), po wybraniu strong attack hA i cA jest prawidłowe (warrior-strong), po wybraniu celu checkAttack jest null a currentAttack jest prawidłowy (warrior-strong). Skutkuje to wykonanie normal attack a to jego powinno anulować a strong attack jest blokowany.
w skrócie po wybraniu celu czyli wykonaniu funkcji selectTarget i createAbility wartość checkAttack jest null na skillu który aktualnie się wybrało.
console logi:
Przechwytywanie2.PNG

JB
  • Rejestracja:około 2 lata
  • Ostatnio:4 dni
  • Lokalizacja:Holandia
  • Postów:843
0

Facet zamęczysz się, daj jakigoś diva z z-Index wysokim ponad wszystkim i tam sobie wyświetlaj.


LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:minuta
  • Postów:8414
1

addEventListener ma opcję once, której nie używasz https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack.

Nie analizowałem dogłębnie, ale być może dlatego, że handler odpalony podczas kliknięcia normal attacku wciąż czeka na await selectTarget();.

Być może potrzebujesz metody, żeby jakoś anulować. Jednak ty już sprawdzasz if (checkAttack !== currentAttack) { więc to niby nie powinno dalej przechodzić.

No i użyłeś w selectTarget ciekawego patternu z zamianą addEventListener na await - sam go używam i dziwię się, że tak mało ludzi go używa, bo tak można uprościć zaawansowane interakcje. Problem w tym, że nie jest out of the box i w zasadzie do tego warto by napisać bibliotekę odpowiednią (tj. wystarczyłoby kilka funkcji, które ułatwiają zarządzanie tym - ja coś tam pisałem i może ogarnę to, żeby z mojego kodu mógłbym korzystać nie tylko ja). Bo wtedy po prostu byś awaity robił odpowiednie.


edytowany 2x, ostatnio: LukeJL
Norbert-2204
  • Rejestracja:9 miesięcy
  • Ostatnio:2 miesiące
  • Postów:12
0
LukeJL napisał(a):

addEventListener ma opcję once, której nie używasz https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

Problem jest taki że gdy wybiorę normal attack, nie wybiorę celu, kliknę strong attack to po wybraniu celu wykonuje się normal attack.

Nie analizowałem dogłębnie, ale być może dlatego, że handler odpalony podczas kliknięcia normal attacku wciąż czeka na await selectTarget();.

Być może potrzebujesz metody, żeby jakoś anulować. Jednak ty już sprawdzasz if (checkAttack !== currentAttack) { więc to niby nie powinno dalej przechodzić.

No i użyłeś w selectTarget ciekawego patternu z zamianą addEventListener na await - sam go używam i dziwię się, że tak mało ludzi go używa, bo tak można uprościć zaawansowane interakcje. Problem w tym, że nie jest out of the box i w zasadzie do tego warto by napisać bibliotekę odpowiednią (tj. wystarczyłoby kilka funkcji, które ułatwiają zarządzanie tym - ja coś tam pisałem i może ogarnę to, żeby z mojego kodu mógłbym korzystać nie tylko ja). Bo wtedy po prostu byś awaity robił odpowiednie.

Użyłem once tak jak napisałeś i skutkowało tym że po użyciu np normal attack, wybraniu celu i ponownym kliknięciu normal attack nie pojawiał się wybór celu.
Tak mam sprawdzenie i sam się dziwie czemu to reaguje na odwrót, dlatego proszę tu o pomoc. Nie rozumiem o co Ci chodzi z "out of the box"
Właśnie tak mi się wydaje że cały czas czeka na selectTarget już po wybraniu innej umiejętności ale po wybraniu np strong attack to obie zmienne sprawdzające są ustawiane pod strong attack.
wychodziło by na to że po kliknięciu normal attack, po czym kliknięciu strong attack, oczekiwanie selectTarget dalej działa na normal attack i ma priorytet więc zmienne zajmujące się sprawdzaniem tego (które w tym momencie są warrior-strong), są zmieniane z powrotem na normal-warrior co skutkuje tym problemem. Mogło by tak być?

LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:minuta
  • Postów:8414
1
Kopiuj
targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {

zauważ, że przypisujesz do każdego buttona handler i nawet jak dasz once, to po kliknięciu przycisku tylko jeden handler się skasuje, a reszta handlerów będzie czekać. Tutaj lepiej byłoby użyć delegacji i podczepić się raz pod element wyżej (pod parenta tych przycisków), a nie pod każdy przycisk osobno.

Nie rozumiem o co Ci chodzi z "out of the box"

Chodzi mi o to, że teraz robisz wszystko ręcznie, podpinasz addEventListenery doraźnie na potrzeby jednej interakcji, przez co potem masz bałagan. Gołe addEventListenery są spoko, jeśli człowiek je ustawi raz i już. Ale jeśli to ma być dynamicznie, tutaj dokładamy addEventListener na potrzeby jednej interakcji itp. to potem ciężko nad tym panować, bo możesz kilka razy ustawić itp.

Można tego uniknąć i zrobić bardziej centralny system zarządzania eventami i zamienić je na promisy (podobnie do tego, co zrobiłeś w selectTarget) i potem robić wszędzie await i np. coś takiego robić (w dużym uproszczeniu)

Kopiuj
async function interaction() {
  while (true) {
    const attackKind = await selectAttackKind(); // user kliknął w przycisk
    const target = (await once('click', '.target')) // i w kolejny
        .target.getAttribute("data-target");
    // możemy atakować
  }
}

przy czym to nie zadziała out of the box, tylko trzeba by samemu albo napisać doraźnie funkcje pomocnicze (takie jak funkcja once w kodzie powyżej) albo skorzystać z biblioteki (nie wiem, czy jest jakaś poza tym, co ja sam kombinuję. Tam musiałem np. myśleć nad sposobem anulowania takich promisów).


edytowany 5x, ostatnio: LukeJL
Norbert-2204
  • Rejestracja:9 miesięcy
  • Ostatnio:2 miesiące
  • Postów:12
0
LukeJL napisał(a):
Kopiuj
targetButtons.forEach((button) => {
      showSelectTarget();
      button.addEventListener("click", (event) => {

zauważ, że przypisujesz do każdego buttona handler i nawet jak dasz once, to po kliknięciu przycisku tylko jeden handler się skasuje, a reszta handlerów będzie czekać. Tutaj lepiej byłoby użyć delegacji i podczepić się raz pod element wyżej (pod parenta tych przycisków), a nie pod każdy przycisk osobno.

Nie rozumiem o co Ci chodzi z "out of the box"

Chodzi mi o to, że teraz robisz wszystko ręcznie, podpinasz addEventListenery doraźnie na potrzeby jednej interakcji, przez co potem masz bałagan. Gołe addEventListenery są spoko, jeśli człowiek je ustawi raz i już. Ale jeśli to ma być dynamicznie, tutaj dokładamy addEventListener na potrzeby jednej interakcji itp. to potem ciężko nad tym panować, bo możesz kilka razy ustawić itp.

Można tego uniknąć i zrobić bardziej centralny system zarządzania eventami i zamienić je na promisy (podobnie do tego, co zrobiłeś w selectTarget) i potem robić wszędzie await i np. coś takiego robić (w dużym uproszczeniu)

Kopiuj
async function interaction() {
  while (true) {
    const attackKind = await selectAttackKind(); // user kliknął w przycisk
    const target = await once('click', '.target') // i w kolejny
        .target.getAttribute("data-target");
    // możemy atakować
  }
}

przy czym to nie zadziała out of the box, tylko trzeba by samemu albo napisać doraźnie funkcje pomocnicze (takie jak funkcja once w kodzie powyżej) albo skorzystać z biblioteki (nie wiem, czy jest jakaś poza tym, co ja sam kombinuję. Tam musiałem np. myśleć nad sposobem anulowania takich promisów).

Próbuję coś wymyśleć z tego przykładu co podałeś ale to raczej za trudne dla mnie, nie ogarniam kompletnie i nie wiem jak to złożyć. Dla mnie za wysokie progi najwidoczniej,Chyba będę musiał porzucić tworzenie tej gry bo brakuje mi wiedzy. No po prostu nie jestem w stanie nad tym zapanować i tworze jakieś dziwactwa które sprawia że działa jeszcze gorzej 😀 ,
coś takiego wytworzyłem teraz ale i tak nie działa: 😅

Kopiuj
async function selectTarget() {
  return new Promise(async (resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
    
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");

    console.log(checkAttack, 'check po wyborze celu');
    console.log(currentAttack, 'current po wyborze celu');

    resolve(selectedTarget);
  });
}

function once(eventName) {
    return new Promise((resolve) => {
      const element = document.querySelector('.target');
      const listener = (event) => {
        element.removeEventListener(eventName, listener);
        resolve(event);  
      };
      element.addEventListener(eventName, listener);
    });
  }
  async function interaction() {
    while (true) {
      const attackKind = await selectAttackKind();  
      console.log("Wybrano atak:", attackKind);
   
      const target = await selectTarget();
      console.log("Wybrano cel:", target);
      
      await executeAttack(attackKind, target);
    }
  }
  
  async function selectAttackKind() {
    return new Promise((resolve) => {
      const attackButtons = document.querySelectorAll(".js-skills");  
      attackButtons.forEach((button) => {
        button.addEventListener("click", async (event) => {
          const abilityName = event.target.getAttribute("data-skill");
          resolve(abilityName);  
        });
      });
    });
  }
  
  async function executeAttack(attackName) {
    switch (attackName) {
      case "warrior-normal-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-normal-attack",
          15,
          20,
          "normal attack",
          1.7,
          null,
          10
        );
        break;
      case "warrior-strong-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-strong-attack",
          20,
          30,
          "strong attack",
          1.7,
          null,
          5
        );
        break;
      
      default:
        console.log("Nieobsługiwany atak:", attackName);
    }
  }
edytowany 2x, ostatnio: Norbert-2204
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:minuta
  • Postów:8414
0
Kopiuj
async function selectTarget() {
  return new Promise(async (resolve) => {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
    
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");

    console.log(checkAttack, 'check po wyborze celu');
    console.log(currentAttack, 'current po wyborze celu');

    resolve(selectedTarget);
  });
}

Tu taka uwaga. Funkcje asynchroniczne zwracają promis (cokolwiek zwrócisz, będzie opakowane w promis). Więc tu wystarczyłoby:

Kopiuj
async function selectTarget() {
    const targetButtons = document.querySelectorAll(".target");
    console.log(checkAttack, "checkAttack przed sprawdzeniem w selectTarget");
    console.log(currentAttack, "currentAttack przed sprawdzeniem w selectTarget");
    showSelectTarget();
     
    const targetEvent = await once('click');  
    const selectedTarget = targetEvent.target.getAttribute("data-target");
 
    return selectedTarget;
}

no ale myślę, że jeśli coś nie działa, to z innego powodu. No i new Promise dalej się przydaje tam, gdzie zamieniasz callbacki na promise.
co dokładnie robi funkcja showSelectTarget?

i tak nie działa: 😅

Co dokładnie nie dziala?


edytowany 1x, ostatnio: LukeJL
Norbert-2204
  • Rejestracja:9 miesięcy
  • Ostatnio:2 miesiące
  • Postów:12
0

showSelectTarget jedynie pokazuje przyciski do wybrania celu, mam też funkcje która chowa przyciski

dopiero teraz miałem czas usiąść, nie działa dlatego że funkcja która służy do obliczania obrażeń wychodzi undefined, nie miałem też czasu do tego usiąść i sprawdzać dlaczego. To i tak mnie dziwi dlaczego wyszło undefined, jestem w momencie gdzie nie bardzo rozumiem co się już dzieje 😅
funkcja ta która wychodzi undefined to jest createAbilityManaPlus z pierwszego posta tego tematu.

może mi się uda dziś coś z tym zrobić to edytuje tego posta

Teraz wygląda to tak:

Kopiuj
async function createAbilityManaPlus(
      event,
      selector,
      minDamage,
      maxDamage,
      infoDisplay,
      buffRatio,
      buffEnhance,
      mp,
      abilityName,
    ) {
      if (event.target.classList.contains(selector)) {
        await selectTarget();
        console.log(checkAttack, "checkAttack w createA przed sprawdzeniem");
        console.log(
          currentAttack,
          "currentAttack w createA przed sprawdzeniem"
        );
        if (ifAttackChanged()) {
          console.log(
            checkAttack,
            "check attack w createA po sprawdzeniu, ",
            currentAttack,
            "currentAttack w createA po sprawdzeniu, ",
            "atak sie zmienił, anulowanie w createAbility, "
          );
          return;
        }
        //
        if (!selectedTarget) {
          return;
        }

        let targetElement = selectMonster(selectedTarget);
        let playerDmg = calculateDmg(minDamage, maxDamage);
        if (buffRatio === null) {
        } else if (buffStats === true) {
          playerDmg = Math.round((playerDmg *= buffRatio));
          buffStats = false;
        }
        if (buffEnhance === null) {
        } else if (buffEnhance === true) {
          playerDmg = Math.round((playerDmg *= enhanceBuff));
          buffEnhance = false;
        }

        reduceHp(selectedTarget, targetElement, playerDmg);
        const playerBlock = event.target.closest(".player-block");
        const heroId = playerBlock.getAttribute("data-hero-id");
        const heroMPElement = playerBlock.querySelector(".mp-amount");
        const hero = heroes.find((hero) => hero.id === heroId);
        gainMana(mp, hero, heroMPElement);
        showInfo(playerDmg, hero, infoDisplay);
        checkAttack = null;
        console.log(checkAttack, "checkAttack po skillu");
        hideSelectTarget();
      }
    }

 async function interaction() {
    while (true) {
      const attackKind = await selectAttackKind();  
      console.log("Wybrano atak:", attackKind);
   
      const target = await selectTarget();
      console.log("Wybrano cel:", target);
      
      await executeAttack(attackKind, target);
    }
  }
  
  async function selectAttackKind() {
    return new Promise((resolve) => {
      const attackButtons = document.querySelectorAll(".js-skills");  
      attackButtons.forEach((button) => {
        button.addEventListener("click", async (event) => {
          const abilityName = event.target.getAttribute("data-skill");
          resolve(abilityName);  
        });
      });
    });
  }
  
  async function executeAttack(attackName) {
    switch (attackName) {
      case "warrior-normal-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-normal-attack",
          15,
          20,
          "normal attack",
          1.7,
          null,
          10,
          abilityName
        );
        break;
      case "warrior-strong-attack":
        
        await createAbilityManaPlus(
          null,  
          "warrior-strong-attack",
          20,
          30,
          "strong attack",
          1.7,
          null,
          5,
          abilityName
        );
        break;
      
      default:
        console.log("Nieobsługiwany atak:", attackName);
    }
  }
}

async function handleAbilityUse(event, abilityName) {
      switch (abilityName) {
        //warrior
        case "warrior-normal-attack":
          currentAttack = "warrior-normal";
          checkAttack = "warrior-normal";
          await interaction()
          break;
        case "warrior-strong-attack":
          currentAttack = "warrior-strong";
          checkAttack = "warrior-strong";
          await interaction()
          break;

const heroContainer = document.getElementById("hero-container");
    heroContainer.addEventListener(
      "click",
      async (event) => {
        if (event.target && event.target.matches(".js-skills")) {
          const abilityName = event.target.getAttribute("data-skill");
          if (checkAttack !== currentAttack) {
            checkAttack = currentAttack;
          }
          await handleAbilityUse(event, abilityName);
        }
      }

Może któraś funkcja jest tam gdzie być nie powinna albo nie działa z innego powodu?
Aktualnie jak klikne np. normal attack to nie pojawia się wybór celu, po ponownym kliknięciu pojawia się ale po wybraniu celu createAbility jest undefined

edytowany 4x, ostatnio: Norbert-2204
LukeJL
ale zwracasz w ogóle cokolwiek z tej funkcji createAbilityManaPlus, żeby nie wychodziło undefined? (w zasadzie undefined wyjdzie dopiero po awaitowaniu, bo asynchroniczne funkcje zwracają zawsze promise, który może najwyżej się rozwiązać do undefined).
Norbert-2204
nie zwracam nic, wcześniej działało mimo że też używałem await. na tej funkcji kończy się cały proces. Po nim tylko chowa wybór celu, odejmuje/pokazuje zadane obrażenia i komu
LukeJL
No to jeśli nie zwrócisz nic z funkcji JS, to zwróci ona domyślnie undefined. Jeśli nie zwrócisz nic z funkcji async, to tak czy siak zwróci ona promise, ale ten promise się rozwiąże do undefined
LukeJL
  • Rejestracja:około 11 lat
  • Ostatnio:minuta
  • Postów:8414
0

Zwróć uwagę, jak używasz zmiennych i jak je deklarujesz, jak je przekazujesz.

Np. zmienna checkAttack nie widzę, żeby była gdziekolwiek deklarowana. Jeśli to zmienna globalna, to powinna być zadeklarowana np. let checkAttack na zewnętrznej części plików (tj. poza funkcjami).

No i jak teraz jest zaimplementowana funkcja selectTarget?

Bo z tego kodu:

Kopiuj
        await selectTarget();
        // ...
        if (!selectedTarget) {
          return;
        }

to wygląda jakby funkcja selectTarget ustawiała jakąś zmienną globalną selectedTarget. Czy na pewno tak jest?

No i o ile ze zmiennymi globalnymi też zadziała, to jakoś lepiej byłoby to zrobić tak, żeby funkcja selectTarget zwracała ten wybrany cel. Wtedy można było uniknąć niepotrzebnej zmiennej globalnej. Ale wtedy musiałbyś tak zrobić:

Kopiuj
const selectedTarget = await selectTarget();

zresztą w ostatnim poście masz też kod:

Kopiuj
 const target = await selectTarget();

Jak dokładnie wygląda ta funkcja selectTarget() w środku? Ustawiasz zmienną globalną czy zwracasz selectedTarget? Czy może jedno i drugie?


edytowany 1x, ostatnio: LukeJL
Norbert-2204
tak, checkAttack i selectedTarget to zmienne globalne, checkAttack to flaga sprawdzająca by efekty się nie nakładały, selectedTarget to bierze dane o wybranym celu, hp i nazwe stworka. Funkcja selectTarget bierze te dane by wiedzieć kogo się atakuje i funkcja zadająca obrażenia czeka na wybranie celu
LukeJL
jeśli będziesz przekazywać bezpośrednio wartości z funkcji (zamiast ustawiać zmienne globalne), to łatwiej ci będzie nad tym zapanować.
VBService
  • Rejestracja:ponad 16 lat
  • Ostatnio:około 2 miesiące
1

Może rozważ użycie <input type="radio"> w taki sposób zamiast <button> wtedy wartości są natywnie przez przeglądarkę sprowadzone do jednego wyboru z każdej kategorii, sczytujesz tylko value z oznaczonych <input type="radio"> (patrz kod poniżej sekcja js).

[ kod przykładu on-line ]

Kopiuj
<div class="player-block" data-hero-id="">
  <div class="description-area">
    <p class="hero-name" data-hero-name=""></p>
    <p class="hp-amount" data-hero-hp="">HP: <span></span></p>
    <p class="mp-amount" data-hero-mp="">MP: <span></span></p>
  </div>

  <div class="ability-area">
    <ul id="focus" class="show">
      <li>
        <input type="radio" name="focus" id="focus-1" value="focus 1">
        <label for="focus-1">focus 1</label>
      </li>
      <li>
        <input type="radio" name="focus" id="focus-2" value="focus 2">
        <label for="focus-2">focus 2</label>
      </li>      
    </ul>
    <ul id="skills">
      <li>
        <input type="radio" name="skills" id="skills-1" value="normal attack">
        <label for="skills-1">normal attack</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-2" value="strong attack">
        <label for="skills-2">strong attack</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-3" value="spear stab">
        <label for="skills-3">spear stab</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-4" value="enrage">
        <label for="skills-4">enrage</label>
      </li>
      <li>
        <input type="radio" name="skills" id="skills-5" value="leap">
        <label for="skills-5">leap</label>
      </li>      
    </ul>
    <ul id="magic">
      <li>
        <input type="radio" name="magic" id="magic-1" value="magic 1">
        <label for="magic-1">magic 1</label>
      </li>
      <li>
        <input type="radio" name="magic" id="magic-2" value="magic 2">
        <label for="magic-2">magic 2</label>
      </li>
      <li>
        <input type="radio" name="magic" id="magic-3" value="magic 3">
        <label for="magic-3">magic 3</label>
      </li>
    </ul>
  </div>

  <div class="actions-area">
    <button data-action="focus">focus</button>
    <button data-action="skills">skills</button>
    <button data-action="magic">magic</button>
  </div>
</div>

<!-- dla testów -->
<pre id="console"></pre>

<style>
  .player-block {
    display: grid;  
    grid-template-columns: 1fr 1fr; 
    grid-template-rows: 1fr 50px; 
    gap: 0px 0px; 
    grid-template-areas: 
      "description-area ability-area"
      "actions-area actions-area";

    width: 60dvw;
    height: 70dvh;
    margin: 1rem auto;
    padding: 1rem;
    font: 300 .9rem/1.05 system-ui, monospace, sans-serif;
    background-repeat: no-repeat;
    background-size: cover;
    background-position: left top;
    border-radius: .25rem;
    user-select: none;
  }
  .description-area { grid-area: description-area; }
  .ability-area { grid-area: ability-area; }
  .actions-area { grid-area: actions-area; }

  .player-block p.hero-name {
    text-transform: uppercase;
    font-size: 180%;
    font-weight: bold;
    text-shadow: 4px 4px 8px black;
  }
  .player-block p.hero-name::first-letter {
    font-size: 135%;
  }  
  .player-block p.hp-amount, p.mp-amount {
    font-weight: bold;
    text-shadow: 4px 4px 8px black;
  }
  .player-block p.hp-amount span, p.mp-amount span  {
    display: inline-block;
    font-weight: normal;
    padding-left: .15rem;
  }
  .player-block p {
    margin: .5rem;
    color: goldenrod;
  }
  .player-block ul {
    display: none;
    list-style: none;
  }
  .player-block ul li + li {
    margin-top: .35rem;
  }
  .player-block ul input[type="radio"] {
    display: none;
  }
  .player-block ul input[type="radio"]:checked ~ label {
    border-color: darkgoldenrod;
    background-color: rgba(218, 165, 32, .9); /* goldenrod */
    color: white;
  }
  .player-block ul label {
    display: block;
    font: 300 .7rem/1.5 system-ui, monospace, sans-serif;
    width: 20ch;
    height: 4ch;
    border-radius: .5rem;
    background-color: rgba(255, 255, 255, .7);
    text-transform: uppercase;
    text-align: center;
    cursor: pointer;
    border: 2px solid black;
    transition: all 150ms;
  }
  .player-block ul label::first-letter {
    font-size: 135%;
  }
  .player-block ul label:hover {
    background-color: rgba(255, 255, 255, .9);
  }
  .player-block .actions-area {
    display: flex;
    justify-content: center;
  }
  .player-block .actions-area button {
    width: 12ch;
    height: 6ch;
    border-radius: .5rem;
    background-color: rgba(255,255,255, .7);
    text-transform: uppercase;
    cursor: pointer;
    transition: background-color 150ms;
  }
  .player-block .actions-area button::first-letter {
    font-size: 140%; 
  }
  .player-block .actions-area button + button {
    margin-left: 1rem;
  }
  .player-block .actions-area button:hover {
    background-color: rgba(255,255,255, .9);
  }

  .show {
    display: block !important;
  }


  #console {
    display: block;
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translatex(-50%);
    width: 65dvw;
    height: 15ch;
    font: 300 .9rem/1 system-ui, monospace, sans-serif;
    background-color: black;
    color: limegreen;
    padding: .5rem;
    overflow-y: auto;
    z-index: 9999;
  }
  #consola::-webkit-scrollbar {
    width: 10px;
  }
  #consola::-webkit-scrollbar-track {
    background: black;
  }
  #consola::-webkit-scrollbar-thumb {
    background: limegreen;
    border-radius: 2px;
  }
</style>

<script>
  const selectedHero = [
    { 
      id:1, 
      name:'Bolok',
      img:'https://pics.craiyon.com/2023-11-03/ce03f7fd90714bc295dbdb688e3ec324.webp',
      hp: 20,
      mp: 45
    }
  ];

  const player_block = document.querySelector('.player-block');
  player_block.addEventListener('click', playerAction);
  player_block.addEventListener('change', handleCheckSelected);

  function playerAction(e) {
    if (e.target.matches('.actions-area button') && e.target.dataset.action) {
      [...player_block
       .querySelectorAll('.ability-area ul')]
        .map(ul => ul.classList.remove('show'));
      player_block
        .querySelector(`.ability-area ul#${e.target.dataset.action}`)
        .classList.add('show');      
    }
  }

  function handleCheckSelected(e) {
    if (e.target.matches('.ability-area input[type="radio"]')) {
      const selected = player_block.querySelectorAll('.ability-area input[type="radio"]:checked');
      if (selected.length < 3) {
        consoleLog(`Wybrano ${selected.length} / 3 : ostatnio wybrany: ${e.target.value}`);
        return;
      }

      let log = `Wybrano 3 / 3 - Start tryb ataku!\nWybrane pozycje:\n`;
      for (const select of selected)
        log += ` + ${select.value}` + '\n';
      consoleLog(log);      
    }   
  }

  function consoleLog(log) {
    const console_ = document.querySelector('#console);
    console_.textContent += log + '\n';
    console_.scrollTop = console_.scrollHeight;    
  }


  player_block.dataset.heroId = selectedHero[0].id;
  player_block.style.backgroundImage = `url(${selectedHero[0].img})`;  
  player_block.querySelector('[data-hero-name]').textContent = selectedHero[0].name;
  player_block.querySelector('[data-hero-hp] span').textContent = selectedHero[0].hp;
  player_block.querySelector('[data-hero-mp] span').textContent = selectedHero[0].mp;
</script>

image


Talk is cheap. Show me the code! - Linus Torvalds
-----------------------------------------------------------------------------------------------------------
Simplicity is a great virtue but it requires hard work to achieve it and education to appreciate it. And to make matters worse: complexity sells better. - Edsger W. Dijkstra
edytowany 5x, ostatnio: VBService
Norbert-2204
  • Rejestracja:9 miesięcy
  • Ostatnio:2 miesiące
  • Postów:12
0

Nie znalazłem rozwiązania którego szukałem ale doszedłem do wniosku że zostanę przy tym co utworzyłem. Z racji że nie ma bezpośredniego sposobu na kasowanie oczekujących operacji musiałem znaleźć chociaż jakąś opcję która nie pozwoli na klikanie czego się chce z efektem takim że wykona się to pierwsze. Zastosowałem pętle z flagami i AbortController i po naprawdę długim czasie myślenia jak poradzić sobie z tym problemem nie znalazłem innego rozwiązania

Kopiuj
async function selectTarget() {
      showSelectTarget();
      let isSelecting = true;
      let abort = new AbortController();
      new Promise (async(resolve, reject) => {
        while(isSelecting) {
          if (waitSelecting) {
            console.log('wykryto kliknięcie');
            waitSelecting = false;
            isSelecting = false; 
            heroContainer.removeEventListener('click', handleClick);
            heroContainer.addEventListener('click', handleClick)
            abort.abort();
            hideSelectTarget();
            reject(selectTarget);
            break; 
          } else if(!waitSelecting) {
            resolve(waitSelecting = true);
            break;
          }
          await new Promise(resolve => setTimeout(resolve, 50));  
        };
      });
      
      return new Promise((resolve) => {
        const targetButtons = document.querySelectorAll(".target");
          
        targetButtons.forEach((button) => {
          button.addEventListener("click", (event) => {
            selectedTarget = event.target.getAttribute("data-target");
            isSelecting = false;
            resolve(selectedTarget);
          });
        });
      });
    };

// tutaj handler na listenery

const heroContainer = document.getElementById("hero-container");
    const handleClick = async (event) => {
      if (event.target && event.target.matches(".js-skills")) {
        const abilityName = event.target.getAttribute("data-skill");
        if (checkAttack !== currentAttack) {
          checkAttack = currentAttack;
        }
        await handleAbilityUse(event, abilityName);
      }
    };
    heroContainer.addEventListener("click", handleClick);

Może kiedyś znajde sposób na anulowanie oczekujących poleceń ale na ten moment to najlepsze do czego doszedłem. Teraz gdy wybierzemy umiejętność i klikniemy inną, cała operacja zostaje anulowana, wybór celu się chowa i po prostu możemy wybrać inną umiejętność

edytowany 1x, ostatnio: Norbert-2204
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)