Jak generować linie między blokami schematu w ASCII art?

0

Przychodzę być może z problemem typowym, być może z nietypowym. Generalnie chodzi mi o algorytm, ale że implementacja ma być w JS (przynajmniej na razie), to być może rozwiązanie będzie zależne od języka.

Tworzę sobie program do wypisywania w konsoli pewnego podzbioru (typu) schematów blokowych; wynikowa forma jest zbliżona (ta sama?) do tzw. ASCII art. Program jest na GitHubie tutaj -> https://github.com/silvuss/silvuss-paad (README jest odrobinę mylące w kwestii parametrów wywołania, muszę poprawić przy okazji).

Obecnie program kuleje, strzela oczyma i rzuca wyjątkami, ale potrafi tworzyć już bloki – w ładnych, równych rządkach. Wygląda to tak:

Przykład 1:

Polecenie

node cli.js -h 0 15 -f ' ' -s 2 ' ' -b 1 '*' -w 5 -p 1 -- 4 p r o g r a m m e r s (UPDATE 2020 Feb 28: wywołanie programu nieaktualne)

produkuje

*****  *****  *****  *****  *****  *****  *****  *****  *****  *****  *****  *****
*   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *
* 4 *  * p *  * r *  * o *  * g *  * r *  * a *  * m *  * m *  * e *  * r *  * s *
*   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *  *   *
*****  *****  *****  *****  *****  *****  *****  *****  *****  *****  *****  *****

Przykład 2:

Polecenie*

node cli.js -h 0 15 -f ' ' -s 3 '-' -b 1 '@' -w 10 -p 1 -c 3 -- @ s o m e k i n d (UPDATE 2020 Feb 28: wywołanie programu nieaktualne)

produkuje

@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@
@        @---@        @---@        @
@ @      @---@ s      @---@ o      @
@        @---@        @---@        @
@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@
------------------------------------
------------------------------------
------------------------------------
@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@
@        @---@        @---@        @
@ m      @---@ e      @---@ k      @
@        @---@        @---@        @
@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@
------------------------------------
------------------------------------
------------------------------------
@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@
@        @---@        @---@        @
@ i      @---@ n      @---@ d      @
@        @---@        @---@        @
@@@@@@@@@@---@@@@@@@@@@---@@@@@@@@@@

Natomiast ja chciałem Was spytać, w jaki sposób mógłbym generować linie pomiędzy tymi blokami? Chodzi mi o coś takiego przykładowo:

A --|  C
    |  |
    |  |
    B  D -- E

Myślałem tak ogólnie nad tym – wydaje się to bardzo skomplikowane, ale nie niemożliwe. Może ktoś zna w miarę prosty sposób.


* Z takiego schematu np. @somekind nie dostanie powiadomienia. Może w przyszłości o tym pomyślę… ;)


UPDATE 2020 Feb 28:

Przepisałem architekturę programu (około półtora raza) i zmieniłem część nazewnictwa (m.in. nazwa programu zmieniła się z paas na paad).

Poprawiłem też README oraz link w tym poście do repozytorium (zmienił się, bo zmieniłem nazwę repozytorium, a to znów z uwagi na zmianę nazwy programu).


UPDATE2: W związku z przepisaniem architektury (tj. tak back-endu, jak i front-endu), podane wyżej dwa wywołania programu oznaczyłem jako nieaktualne.


UPDATE3: Zdecydowałem się spróbować przepisać architekturę kolejny raz, ale tym razem w TypeScripcie. Z dwóch głównych powodów: (1) obecna architektura kłuje mnie w oczy skupieniem zbyt obszernej logiki w niektórych funkcjach oraz niezbyt dobrą hermetyzacją; (2) mam wrażenie, że typescriptowe type annotations będą wyglądać bardziej przejrzyście niż obecne komentarze JSDoc (mogę się mylić).

1

http://lisperati.com/vijual/ - screenshoty u dołu strony l.

Teraz już z górki, wystarczy przepisać na język docelowy, bądź znaleźć gotowca ;)

0

@Spine: dziękuję; ale mnie idzie o to, że chcę to sam zrobić. ;) Jeśli nie będę mieć pomysłów, to ostatecznie spróbuję zrozumieć czyjeś rozwiązanie…


UPDATE 2020 Feb 28:

Obecnie (po przepisaniu architektury, o którym wspominam w update do pierwszego postu w tym wątku) widzę to tak: algorytm generowania "linii" między "pudełkami" – zmieniłem nazwę z "bloków" (blocks) na "pudełka" (boxes) – zamierzam opakować w funkcję createGraphDiagram (o nazwie o budowie analogicznej do budowy nazwy funkcji createDiagram). Jako główną część tego algorytmu planuję użyć już stworzonego przeze mnie algorytmu wyznaczania ścieżek na diagramie, zaimplementowanego w funkcji traverse (https://github.com/silvuss/silvuss-paad/blob/master/app/back-end3/diagram/traverse.js#L22). Algorytm generowania "linii" między "pudełkami" mógłby wyglądać tak (w pseudokodzie, niemniej w pewnej mierze zgodnie z obecną architekturą programu):

createGraphDiagram(graph):
1. diagramBoxes = []
2. diagramLinks = []
3. for each node in graph.nodes:
  3.1. box = createBox(node.data)
  3.2. diagramBox = createDiagramBox(box)
  3.3. diagramBoxes.add(diagramBox)
  3.4. for each relatedNode in node.relatedNodes:
    3.4.1. repeat steps from 3.1 to 3.4
    3.4.2. diagramLink = createDiagramLink(node, relatedNode)
    3.4.3. diagramLinks.add(diagramLink)
4. return {
  toString() {
    // tutaj tworzenie ciągu znaków, wykorzystując kolekcje diagramBoxes i diagramLinks
  }
}

UPDATE2:

Zapomniałem o dwóch rzeczach:

  • sprawdzaniu, czy dane "pudełko diagramowe" już istnieje w kolekcji, żeby nie tworzyło go podwójnie dla każdej pary połączonych węzłów (dla połączenia – link – takie sprawdzanie nie ma sensu; algorytm właśnie powinien stworzyć dwa połączenia dla każdej pary połączonych węzłów);

  • podawaniu kolekcji diagramBoxes oraz diagramLinks jako parametrów funkcji createDiagramBox oraz createDiagramLink (Dlaczego w ogóle? Ponieważ te funkcje mają za zadanie określić pozycję danego "pudełka" oraz wszystkich jego połączeń z innymi "pudełkami" na diagramie (produkując owo "pudełko programowe", diagramBox). W związku z tym muszą znać położenie ostatnio wypozycjonowanego pudełka. Dałem całe kolekcje zamiast tylko ostatnich elementów wyłącznie dla uproszczenia pseudokodu funkcji createGraphDiagram (przez to bardziej skomplikowany byłby pseudokod tych dwóch funkcji – gdybym go napisał)).

Zaktualizowana wersja poniżej. Uściśliłem też punkt dot. powtarzania kroków, dodając fragment using relatedNode instead of node.

createGraphDiagram(graph):
1. diagramBoxes = []
2. diagramLinks = []
3. for each node in graph.nodes:
  3.1. box = createBox(node.data)
  3.2. diagramBox = createDiagramBox(box, diagramBoxes, diagramLinks)
  3.3. diagramBoxes.add(diagramBox)
  3.4. for each relatedNode in node.relatedNodes where not diagramBoxes.contains(diagramBox => diagramBox.id == relatedNode.id):
    3.4.1. repeat steps from 3.1 to 3.4 using relatedNode instead of node
    3.4.2. diagramLink = createDiagramLink(node, relatedNode, diagramBoxes, diagramLinks)
    3.4.3. diagramLinks.add(diagramLink)
4. return {
  toString() {
    // tutaj tworzenie ciągu znaków, wykorzystując kolekcje diagramBoxes i diagramLinks
  }
}

PS. Zakładam tutaj, że wspomniana funkcja traverse jest używana w metodach createDiagramBox oraz createDiagramLink.


UPDATE3: Poniżej wersja powyższego algorytmu w postaci bardziej przystępnej JavaScriptowi (nadal w pseudokodzie); wydzieliłem kod, który jest powtarzany, do osobnego podprogramu:

createGraphDiagram(graph):
1. diagramBoxes = []
2. diagramLinks = []
3. for each node in graph.nodes:
  3.1. processNode(
    node,
    (diagramBox) => diagramBoxes.add(diagramBox),
    (diagramLink) => diagramLinks.add(diagramLink)
  )
4. return {
  toString() {
    // tutaj tworzenie ciągu znaków, wykorzystując kolekcje diagramBoxes i diagramLinks
  }
}

processNode(node, updateDiagramBox, updateDiagramLink):
1. box = createBox(node.data)
2. diagramBox = createDiagramBox(box, diagramBoxes, diagramLinks)
3. updateDiagramBox(diagramBox)
4. for each relatedNode in node.relatedNodes where not diagramBoxes.contains(diagramBox => diagramBox.id == relatedNode.id):
  4.1. processNode(relatedNode, updateDiagramBox, updateDiagramLink)
  4.2. diagramLink = createDiagramLink(node, relatedNode, diagramBoxes, diagramLinks)
  4.3. updateDiagramLink(diagramLink)

Jeżeli ktoś zauważy jakieś nieścisłości/błędy, pisać. Ogólnie przydadzą mi się wszelkie wskazówki czy uwagi.


UPDATE4: Poniżej wersja algorytmu w postaci mniej javascriptowej (staram się znaleźć taki pseudokod, który by był najlepszym kompromisem między czytelnością napisanego w nim kodu a bliskością do składni JavaScriptu); przekształciłem zwracany obiekt JavaScriptu z p. nr 4 w podprogramie createGraphDiagram do postaci osobnego podprogramu:

createGraphDiagram(graph):
1. diagramBoxes = []
2. diagramLinks = []
3. for each node in graph.nodes:
  3.1. processNode(
    node,
    (diagramBox) => diagramBoxes.add(diagramBox),
    (diagramLink) => diagramLinks.add(diagramLink)
  )
4. return makeGraphDiagramString(diagramBoxes, diagramLinks)

processNode(node, updateDiagramBoxes, updateDiagramLinks):
1. box = createBox(node.data)
2. diagramBox = createDiagramBox(box, diagramBoxes, diagramLinks)
3. updateDiagramBoxes(diagramBox)
4. for each relatedNode in node.relatedNodes where not diagramBoxes.contains(diagramBox => diagramBox.id == relatedNode.id):
  4.1. processNode(relatedNode, updateDiagramBox, updateDiagramLink)
  4.2. diagramLink = createDiagramLink(node, relatedNode, diagramBoxes, diagramLinks)
  4.3. updateDiagramLinks(diagramLink)

makeGraphDiagramString(diagramBoxes, diagramLinks):
  // tutaj tworzenie ciągu znaków, wykorzystując kolekcje diagramBoxes i diagramLinks

PS. Przepraszam za używanie wymiennie terminów "funkcja" i "podprogram"; sam nie wiem, który z nich jest lepszy; obecnie skłaniam się ku "podprogramowi" jako terminowi niespotykanemu szerzej w kontekście JavaScriptu (przynajmniej niespotykanemu szerzej przeze mnie).


UPDATE5: Poprawiłem powyższy kod podprogramu processNode, bo zawierał błędne nazwy. Zamieniłem updateDiagramBox na updateDiagramBoxes oraz updateDiagramLink na updateDiagramLinks.


UPDATE6: (Przeniosłem informację o kolejnym przepisaniu architektury do pierwszego posta; myślę, że tam będzie bardziej odpowiednia, jako że nie dotyczy wspomnianego w tym poście algorytmu generowania linii).

0

Aktualizacja: przepisałem back-end aplikacji na TypeScript. Front-end będzie prawdopodobnie prostszy, więc na razie odpuszczam; konfigurację podawaną docelowo z linii komend tymczasowo zmockowałem.

Co do samego generowania linii: ukułem algorytm (wspomniany w poście wyżej), który działa mniej-więcej w ten sposób: chodzi po "komórkach" i sprawdza, czy dana komórka jest "wolna"; jeśli tak, to dodaje ją do ścieżki – a jeśli nie, to nie. Wynikowe ścieżki generuję dwa razy: raz – dla diagramu "zwykłego" (nazwanego diagram), a dwa – dla diagramu "grafowego" (nazwanego graphDiagram), czyli diagramu tworzonego na podstawie grafu.

Aplikacja w sumie powinna działać – ale nie działa. Problem jest w algorytmie – a w każdym razie największy problem, jak go oceniam; z innymi wydaje się, że sobie poradziłem / poradzę. Zamieszczam kod algorytmu poniżej – może ktoś będzie wiedział.

Traversal

To jest klasa z metodą traverse, czyli implementacją algorytmu.

import Deque from "../../utils/deque"; // moja implementacja kolejki dwukierunkowej – nie czepiajcie się…
import Move from "../space/move";
import MyPosition from "./my-position";
import Step from "./step";

/**
 * Represents a traversal on a set of positions.
 */
export default class Traversal {
    private path: Deque<Step>;
    private forbiddenMoves: { from: MyPosition, to: MyPosition }[];
    private isTargetPosition!: (myPosition: MyPosition) => boolean;
    private isPositionForbidden!: (myPosition: MyPosition) => boolean;

    constructor() {
        this.path = new Deque<Step>();
        this.forbiddenMoves = [];
    }

    private getLastStep(): Step {
        return this.path.getLastElement();
    }

    private getPreviousPosition(): MyPosition | undefined {
        return this.getLastStep().previousPosition;
    }

    private getCurrentPosition(): MyPosition {
        return this.getLastStep().currentPosition;
    }

    private stepBack(): void {
        this.path.popBack();
    }

    private isSteppingBackMove(move: Move | undefined): boolean {
        const lastStep: Step = this.getLastStep();
        return lastStep.moveThatLed === undefined
            ? false
            : lastStep.moveThatLed.isEqualTo(move);
    }

    private move(move: Move): void {
        const currentPosition: MyPosition = this.getCurrentPosition();
        this.path.pushBack(
            new Step(
                currentPosition,
                move,
                move.forward.displaceFrom(currentPosition)
            )
        );
    }

    private movedBeforeBetween(myPosition1: MyPosition, myPosition2: MyPosition): boolean {
        return this.path.find((step) =>
            step.previousPosition === undefined
                ? false
                : step.previousPosition.isEqualTo(myPosition1)
                && step.currentPosition.isEqualTo(myPosition2)
        ) !== undefined;
    }

    private evaluateNextPosition(move: Move): MyPosition {
        return move.forward.displaceFrom(this.getCurrentPosition());
    }

    private isMoveForbiddenBetween(
        myPosition1: MyPosition, myPosition2: MyPosition
    ): boolean {
        return this.forbiddenMoves.find((fm) =>
            fm.from.isEqualTo(myPosition1) && fm.to.isEqualTo(myPosition2)
        ) !== undefined;
    }

    private forbidMoveBetween(myPosition1: MyPosition, myPosition2: MyPosition): void {
        console.debug(`\tForbidding move from ${myPosition1} to ${myPosition2} ...`);
        this.forbiddenMoves.push({ from: myPosition1, to: myPosition2 });
    }

    private findPossibleMoveFrom(myPosition: MyPosition): Move | undefined {
        return MyPosition.moves.find((m) => {
            const nextPosition: MyPosition = this.evaluateNextPosition(m);
            return !this.isSteppingBackMove(m)
                && !this.isMoveForbiddenBetween(myPosition, nextPosition)
                && !this.movedBeforeBetween(
                    this.getCurrentPosition(), nextPosition
                )
                && !this.isPositionForbidden(nextPosition);
        });
    }

    setWhatPositionsTarget(
        isTargetPosition: (myPosition: MyPosition) => boolean
    ): Traversal {
        this.isTargetPosition = isTargetPosition;
        return this;
    }

    setWhatPositionsForbidden(
        isForbiddenPosition: (myPosition: MyPosition) => boolean
    ): Traversal {
        this.isPositionForbidden = isForbiddenPosition;
        return this;
    }

    setStartPosition(startPosition: MyPosition): Traversal {
        const stepZero: Step = new Step(undefined, undefined, startPosition);
        this.path.pushBack(stepZero);
        return this;
    }

    traverse(): Deque<Step> {
        let i = 0;
        while (true) {
            const currentPosition: MyPosition = this.getCurrentPosition();
            console.debug(`Now on ${currentPosition} (Iteration no. ${++i})`);

            if (this.isTargetPosition(currentPosition)) {
                console.debug(`Target reached at ${currentPosition}`);
                return this.path;
            }

            const possibleMove: Move | undefined
                = this.findPossibleMoveFrom(currentPosition);
            if (possibleMove === undefined) {
                const previousPosition: MyPosition | undefined
                    = this.getPreviousPosition();

                if (previousPosition === undefined) {
                    console.debug(`Cannot found target`);
                    return this.path;
                }

                this.forbidMoveBetween(previousPosition, currentPosition);
                this.stepBack();
            } else {
                this.move(possibleMove);
            }
        }
    }
}

W taki zaś sposób metoda traverse jest wywoływana:

// Klasa Board
findPath(
        startPosition: MyPosition,
        isTargetPosition: (myPosition: MyPosition) => boolean,
        isPositionForbidden: (myPosition: MyPosition) => boolean
    ): Deque<Step> {
        const traversal: Traversal
            = new Traversal()
                .setStartPosition(startPosition)
                .setWhatPositionsTarget(isTargetPosition)
                .setWhatPositionsForbidden(isPositionForbidden);
        const path: Deque<Step> = traversal.traverse();
        return path;
    }

Prawdopodobnie do zrozumienia kodu będą potrzebne jeszcze następujące trzy klasy:

Move

import Displacement from "../space/displacement";
import IEqualComparable from "../../utils/interfaces/i-equal-comparable";

/**
 * Represents a subset of all possible displacements from a point to another
 *  one.
 */
export default class Move implements IEqualComparable<Move> {
    readonly name: string;
    readonly forward: Displacement;
    readonly backward: Displacement;

    constructor(name: string, forward: Displacement, backward: Displacement) {
        this.name = name;
        this.forward = forward;
        this.backward = backward;
    }

    isEqualTo(move: Move | undefined): boolean {
        return move === undefined ? false : this.name === move.name;
    }
}

MyPosition

W Node jest już interfejs o takiej nazwie, dlatego też dodałem przedrostek my.

import Coordinate from "../space/coordinate";
import Point from "../space/point";
import Displacement from "../space/displacement";
import Move from "../space/move";

/**
 * Represents a point in a space, which point "knows" how to move to another
 *  one.
 */
export default class MyPosition extends Point {
    private static readonly displacements:
        {
            topward: Displacement,
            rightward: Displacement,
            bottomward: Displacement,
            leftward: Displacement,
        }
        = {
            topward:
                new Displacement(
                    (myPosition: MyPosition) =>
                        new MyPosition(
                            myPosition.x, myPosition.y.getDecremented()
                        )
                ),
            rightward:
                new Displacement(
                    (myPosition: MyPosition) =>
                        new MyPosition(
                            myPosition.x.getIncremented(), myPosition.y
                        )
                ),
            bottomward:
                new Displacement(
                    (myPosition: MyPosition) =>
                        new MyPosition(
                            myPosition.x, myPosition.y.getIncremented()
                        )
                ),
            leftward:
                new Displacement(
                    (myPosition: MyPosition) =>
                        new MyPosition(
                            myPosition.x.getDecremented(), myPosition.y
                        )
                )
        };

    static readonly moves: Move[]
        = [
            new Move(
                "topward",
                MyPosition.displacements.topward,
                MyPosition.displacements.bottomward
            ),
            new Move(
                "rightward",
                MyPosition.displacements.rightward,
                MyPosition.displacements.leftward
            ),
            new Move(
                "bottomward",
                MyPosition.displacements.bottomward,
                MyPosition.displacements.topward
            ),
            new Move(
                "leftward",
                MyPosition.displacements.leftward,
                MyPosition.displacements.rightward
            )
        ];

    protected constructor(x: Coordinate, y: Coordinate) {
        super(x, y);
    }
}

Step

import Move from "../space/move";
import MyPosition from "./my-position";
import IEqualComparable from "../../utils/interfaces/i-equal-comparable";

/**
 * Represents a step in a traversal on a set of positions.
 */
export default class Step implements IEqualComparable<Step> {
    previousPosition: MyPosition | undefined;
    moveThatLed: Move | undefined;
    currentPosition: MyPosition;

    constructor(
        previousPosition: MyPosition | undefined,
        moveThatLed: Move | undefined,
        currentPosition: MyPosition
    ) {
        this.previousPosition = previousPosition;
        this.moveThatLed = moveThatLed;
        this.currentPosition = currentPosition;
    }

    isEqualTo(step: Step | undefined): boolean {
        const previousPositionsEqual: boolean
            = (this.previousPosition === undefined
                && step!.previousPosition === undefined)
            || this.previousPosition!.isEqualTo(step!.previousPosition);
        const movesThatLedEqual: boolean
            = (this.moveThatLed === undefined
                && step!.previousPosition === undefined)
            || this.moveThatLed!.isEqualTo(step!.moveThatLed);
        const currentPositionsEqual: boolean
            = this.currentPosition.isEqualTo(step!.currentPosition);

        return step === undefined
            ? false
            : previousPositionsEqual
            && movesThatLedEqual
            && currentPositionsEqual;
    }
}

(Tak, wydaje się, że nasze forum nie obsługuje kolorowania składni TypeScriptu).

Wiem, że do oceny działania algorytmu potrzeba by całego programu; ale raz, że nie chcę kodu na GitHubie umieszczać – bo nie działa – a dwa, że w treści posta umieściwszy byłby on całkiem nieczytelny (plików *.ts – licząc back-end i pliki utils – jest 35).

Jeśli więc ktoś chciałby się nad tym pochylić…


PS. Kod może wydawać się "ściśnięty" wizualnie na szerokość, bo w projekcie celuję w 80-kolumnowe linie. Tak po prostu przyjąłem – nie wiem, czy to źle, czy dobrze.


PS2. Jakby ktoś miał jakieś uwagi do kodu – zbyt rozwlekły, zbyt lakoniczny, za wiele metod, za mało metod, zbyt generyczny, za mało generyczny… to pisać! Przyda mi się każda uwaga.

0

mam wrażenie, że typescriptowe type annotations będą wyglądać bardziej przejrzyście niż obecne komentarze JSDoc (mogę się mylić).

O ile nie przepadam z TypeScriptem, to tutaj mógłby się faktycznie przydać. Szczególnie, że tam i tak robisz swój customowy system typów ( https://github.com/silvuss/silvuss-paad/blob/master/app/utils/types.js ). Chociaż oczywiście mam świadomość, że dynamiczny system typów to nie to samo co statyczny, bo rozwiązuje podobny, ale nieco inny problem (bo nie wszystko da się wyłapać statycznie), więc możliwe, że jest faktycznie potrzebny (szczególnie, ze tam robisz też trochę większej logiki przy wykrywaniu "typu").

} else if (arguments.length > 2) {
throw errors.createTooManyArgumentsError(arguments.length);
}

Tu już są braki w samym języku JavaScript, że nie można zwalidować liczby argumentów (chociaż mi to nie przeszkadza, tym niemniej jest to pewien brak). Aczkolwiek TypeScript rozwiązuje ten problem: http://www.typescriptlang.org/docs/handbook/functions.html#functions
(...)number of arguments given to a function has to match the number of parameters the function expects.

W sumie teraz dopiero czytam, że już to przepisujesz na TypeScript (przeczytałem pierwszy post i myślałem, że dopiero się zastanawiasz).

Aplikacja w sumie powinna działać – ale nie działa.

Co to znaczy nie działa? Jaki jest dokładnie efekt? A jaki powinien byc?

0

@LukeJL: przepraszam, że nic nie piszę. Całkiem nie miałem ochoty wracać do tego.

Jednak teraz chcę zrobić małą aktualizację; aczkolwiek to coś innego, niż to, o co pytałeś. Właśnie skończyłem pisać implementować ten sam / podobny algorytm, tym razem w JS (ponownie w JS, tylko inaczej zaimplementowane). Być może logika jest trochę inna, dlatego piszę "ten sam / podobny"; nie pamiętam. W każdym razie wynik jest taki, że wydaje się działać. I nawet widać to w konsoli na planszy.

To powiedziawszy, myślę, że gdy w końcu będę mieć siłę do tego wrócić, to spróbuję algorytm w TS poprawić względem obecnie napisanego w JS. Na razie – cieszę się z małego sukcesu. Choć wiem, że ten sukces może niebawem zmienić się w porażkę – gdy znajdę bug. To, że na jednej planszy działa, nie znaczy, że zadziała na każdej. Mogłem coś przeoczyć…

0

@LukeJL: aktualizacja. Poprawiłem algorytm w TS. Ale nie wiem, czy działa, bo nie mogę przetestować… tutaj wątek dotyczący błędu, jaki otrzymuję:
Błąd "Cannot find module '../config.json'. Consider using '--resolveJsonModule' to import module with '.json' extension"


UPDATE: OK, rozwiązałem ten problem przez obejście – require zamiast import (więcej w zalinkowanym wątku).


PS. Ale nie mogę jeszcze napisać tutaj, czy działa, bo na razie chcę całość w TS poprawić. Potem będę sprawdzać, czy działa. Jakoś nie mam głowy teraz do samego algorytmu.

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