Skip to content

Instantly share code, notes, and snippets.

@kino6052
Last active February 8, 2025 18:59
Show Gist options
  • Save kino6052/a865936c25dd7b91633bcb81c42c3983 to your computer and use it in GitHub Desktop.
Save kino6052/a865936c25dd7b91633bcb81c42c3983 to your computer and use it in GitHub Desktop.
React TicTacToe

Title: The Moment of Change: Rethinking Long-Term Planning in UI Development


Introduction

Modern UI development thrives on rapid iteration, but this agility often comes at the cost of long-term sustainability. Teams find themselves trapped in a cycle of rewriting code as frameworks evolve, implementation details shift, and abstractions leak. The root of this fragility lies in our over-reliance on concrete frameworks rather than architectural principles—a problem that demands a fundamental rethinking of how we plan, design, and build.

This article explores why traditional long-term planning fails in today’s UI ecosystem, proposes solutions to decouple code from volatile implementation details, and envisions a future where UI development is driven by conceptual clarity rather than framework churn.


The Core Issue: Frameworks as a Trap

Modern UI development is tightly coupled to frameworks like React, Angular, or Vue. While these tools accelerate initial development, they bind teams to their implementation specifics:

  • Frameworks evolve: What’s cutting-edge today becomes legacy tomorrow.
  • Leaky abstractions: Components often depend on framework-specific lifecycle methods or state management.
  • Rigid mental models: Developers equate architecture with framework patterns (e.g., Angular’s MVVM), limiting adaptability.

TypeScript offers temporary relief by catching errors early and enabling AI-assisted refactoring, but it’s not enough. To future-proof code, we need architectural insulation—a way to abstract logic from frameworks while retaining flexibility.


Proposed Solutions

1. Architectural Approach: Component-Based MVVM

True architecture isn’t about frameworks—it’s about designing systems that isolate concerns and postpone decisions. Let’s rethink the classic React TicTacToe tutorial to illustrate:

Key Principles

  • Component-as-Entity: Treat components like domain entities (e.g., Game, Board, Square) with clear responsibilities.
  • ViewModel as Props: Instead of framework-specific state, pass a props object encapsulating logic (e.g., mapStateToBoardProps).
  • Dependency Injection: Decouple I/O (e.g., fetch requests) from components for easier testing and decision postponing.

Example

// Game component manages state but delegates rendering to Board  
export default function App() {  
  const [state, setState] = useState<TState>(...);  
  return <Game boardProps={mapStateToBoardProps(state, setState)} ... />;  
}  

// Board renders squares without knowing state management details  
export function Board({ squareProps, status }: TBoardProps) {  
  return (  
    <>  
      <div className="status">{status}</div>  
      {squareProps.map(row => <div className="board-row">...</div>)}  
    </>  
  );  
}  

export const mapStateToBoardProps = getMapStateToProps(dependencies as TOutsideWorldDependencies);

Here, mapStateToBoardProps acts as a lightweight ViewModel, translating state into framework-agnostic props. This pattern works with React today but could adapt to Svelte or Angular tomorrow.


2. Conceptualization: Ontologies as a Single Source of Truth

The rise of AI and declarative programming demands a shift from coding to conceptualizing. Domain-Driven Design (DDD) attempted this with "ubiquitous language," but its abstractions often leak (it jumps to OOP development directly, whereas OOP development is a detail, a true ubiquitous language can happen even without development team in a company) A better approach:

Top-Down Ontology Design

  1. Start with mocks: Define UI flows and interactions first.
  2. Derive entities: Let components (e.g., Square, Move) emerge from user needs.
  3. Automate relationships: Use tools like the Owl ontology language to formalize concepts (e.g., :Square :parent :Entity).

Example

:Game :parent :Entity .  
:Move :parent :Action .  
:Board :parent :Entity .  

This ontology becomes the system’s backbone, enabling AI-driven refactoring (e.g., merging duplicate concepts) and propagating changes across designs, UI, and backend.
Even though we started with development with designs, it doesn't mean that ontology is secondary. It just means that we can't start with ontology directly because we will most likely create unecessary entities that will not scale well.


Predictions: The Future of UI Development

As Henric Altschuller famously said that there is a law of technical systems increasing the degree of their idealism over time, this tred is really going to continue, software will keep getting more and more declarative

The UI developer of tomorrow will resemble an applied philosopher-devops hybrid:

  1. Ontology-Driven Tools: Systems will auto-generate code from declarative ontologies.
  2. AI as Collaborator: AI will refactor ontologies, suggest simplifications, and update dependent code.
  3. Framework-Agnostic Layers: UI code will depend on designs, designs on ontologies, and backends on contracts—not frameworks.

Imagine a workflow where:

  • A designer updates a Figma mock.
  • The ontology updates, triggering AI to refactor component interfaces.
  • The backend adjusts its API schema automatically.

Conclusion

Long-term planning in UI development isn’t about predicting the future—it’s about building systems that embrace change. By decoupling logic into architectural layers and grounding work in formalized ontologies, teams can escape the framework upgrade treadmill.

The future belongs to those who treat code as a byproduct of clear thinking, not the endpoint. Let’s stop writing for today’s React or Angular and start designing for the systems of tomorrow.


For code examples and ontology details, refer to the TicTacToe implementation and OWL ontology discussed in this article.

Переосмысление долгосрочного планирования в UI разработке

DesignOps

Введение

Современная UI-разработка живёт быстрыми темпами. Мы постоянно создаем и меняем код, чтобы соответствовать новым трендам, но часто это делается «на коленке» и без долгосрочного видения. В итоге уже через пару месяцев всё, что казалось суперсовременным, превращается в легаси из-за смены моды фреймворков и прочих деталей реализации.

Эта статья расскажет, почему традиционное долгосрочное планирование уже не работает, как можно отделить основную логику от конкретных технологий и какие инструменты помогут нам быть гибче в будущем.

Проблема: Ловушка фреймворков

Сегодня большинство UI-разработчиков завязаны на конкретных фреймворках вроде React, Angular или Vue. Да, они помогают быстро запустить проект, но и накладывают свои особенности:

  • Постоянное обновление: Что сегодня в тренде, завтра уже устарело.
  • Протекающие абстракции: Логика часто зависит от специфических особенностей фреймворка (логика привязывается к редаксу или к хукам Реакта).
  • Ограничивает мышление: Мы начинаем путать архитектуру приложения с особенностями выбранного инструмента.

Хотя TypeScript помогает ловить ошибки еще на этапе написания кода и делает рефакторинг впринципе возможным, этого все же недостаточно. Нам нужна архитектурная изоляция, чтобы логика приложения оставалась независимой от мельчайших деталей реализации.

Решения: как выйти из капкана деталей

Есть одна теория, которая гласит, что технические решения, в том числе и UI-приложения, подчиняются определённым закономерностям, и один из главных законов – это стремление к повышению степени идеальности. То есть, части системы перестают быть привязанными к конкретике и могут быть реализованы вне жёсткой связки с ней.

Эта теория – ТРИЗ (Теория решения изобретательских задач). В рамках ТРИЗ идеальным решением считается такое, при котором система выполняет свою функцию, но сама система как бы исчезает.

Пример: Автоматические стеклоочистители

Рассмотрим автомобильные стеклоочистители. Раньше водитель должен был сам очищать лобовое стекло от дождя и грязи, останавливаясь и вручную протирая его. Затем появились механические дворники с ручным управлением, а позже – автоматические стеклоочистители, работающие по таймеру.

Но если следовать принципам ТРИЗ и увеличивать степень идеальности, то идеальный вариант – это когда стекло само очищается без механических дворников. Так появились гидрофобные покрытия, благодаря которым вода просто скатывается с поверхности стекла.

В этом случае система очистки стекла исчезает как физический механизм, но функция (удержание стекла чистым) остаётся и выполняется ещё эффективнее.

Решение для UI-разработки

Этот же принцип можно перенести в UI-приложения, где мы повышаем степень идеальности:

  • Замена сложных зависимостей на абстракции, позволяющие подменять конкретную реализацию без изменения кода приложения.
  • Использование декларативности, где мы делаем что-то не вручную, а просто описываем в общих чертах, а система сама решает, как выполнить детали.

Чем меньше конкретных деталей и привязок внутри системы, тем более она гибкая, лёгкая и способная адаптироваться к изменениям.

В этой статье мы рассмотрим концепции на примере реализации игры в крестики-нолики из документации Реакт, но измененной нами.

1. Обобщение и абстракция

Обощение и абстракция - это то чем занимается архитектура. Она позволяет сделать части независимыми и отсюда легкотестируемыми и отложить принятие решений о деталях как можно дальше в будущее.

Истинная архитектура приложения – это про четкое разделение задач и ответственность каждого компонента, а не про жесткую привязку к определенному фреймворку.

Ключевые принципы:

  • Компонент как сущность: Каждый UI-компонент (например, Game, Board, Square) должен быть самостоятельной единицей с ясными обязанностями, а также часть онтологии (универсального языка для всей компании). Названия компонентов не придуманы разработчиком. Они появляются на этапе дизайна, после того как принято общее решение о том, что это соответствует тому, что мы хотим достичь.
## Пример онтологии, где сущности находятся в отношении is-a
## Т.е. предок может быть заменен родителем без потери коректности

Entity
├── Game
├── Square
├── Mark
├── Player
└── Board

GameTopic
├── Game
├── SquareTopic
|   ├── Mark
│   └── Move
├── PlayerTopic
│   └── Player
└── BoardTopic
    └── Board
  • ViewModel через пропсы: Вместо привязки к внутреннему состоянию конкретного фреймворка передавайте в компонент все необходимые данные через пропсы. Это делает компонент независимым и гибким.
// Game component composes state and view model with dependencies
export default function App() {
  const [state, setState] = useState<TState>(...);
  return <Game boardProps={mapStateToBoardProps(state, setState)} ... />;
}

// Board renders squares without knowing state management details
export function Board({ squareProps, status }: TBoardProps) {
  return (
    <>
      <div className="status">{status}</div>
      {squareProps.map(row => <div className="board-row">...</div>)}
    </>
  );
}
  • Инъекция зависимостей: Выносите операции ввода-вывода (например, запросы к серверу) в отдельные слои, чтобы упростить тестирование и заменить реализацию при необходимости.
// Converts game state into props for Square components arranged in rows
export const getMapStateToSquareProps =
  ({
    someAsyncDependency, // this dependency illustrates how IO can be injected into the component
  }: {
    someAsyncDependency: () => Promise<void>;
  }) =>
  (state: TState, setState: (state: TState) => void): TSquareProps[][] => {
    // Group squares into rows of 3
    // Create array of rows, each containing 3 squares
    const rows: TSquareProps[][] = [];

    setRow((i) => {
      value: state.squares[i],
      onSquareClick: async () => {
        try {
          await someAsyncDependency(); // calling async dependency
          makeMove(i, state, setState); // calling business logic
        } catch (error) {
          console.error(error);
        }
      },
      index: i,
    })

    return rows;
  };

const mapStateToSquareProps = getMapStateToSquareProps({
  someAsyncDependency: async () => {
    await new Promise((resolve) => setTimeout(resolve, 500));

    alert("Async dependency has been resolved");
  },
});

2. Повышение уровня декларативности

Современные тренды (в особенности AI и декларативное программирование) скрывают множество деталей реализации, и нам важно сосредоточиться на концепциях.

Почему онтологии?

  • От идеи к сущности: Начиная с макетов и моков, описывающих взаимодействие пользователя с приложением, можно постеенно постепенно вырабатывайте сущности (например, Square, Move, Player) не плодя сущности заранее.
  • Автоматизация изменений: Использование инструментов вроде языка онтологий OWL, чтобы формализовать концепции и отношения между ними, может позволить AI автоматически рефакторить и упрощать структуру этой же онтологии, когда изменятся бизнес-требования, одновременно с этим позволить иметь общий для всей компании "язык" - онтологюю.

Такой подход поможет избежать создания лишних сущностей и держать систему простой и гибкой. Это позволит изменить детали реализации, не переделывая всю архитектуру.

Взгляд в будущее: UI-разработка без привязки к фреймворкам

Как уже было сказано, технические решения, в том числе и UI-приложения, подчиняются определённым закономерностям, и один из главных их законов – это стремление к повышению степени идеальности. Т.е. Детали уходят на второй план, а обобщения на первый.

Хочу предположить, что в будущем роль UI-разработчика будет не только «писать код», а скорее «строить систему идей», где:

  • Инструменты будут на базе онтологий: Код будет генерироваться автоматически из декларативных описаний, где онтология служит единым источником правды.
  • AI в роли ассистента: Искусственный интеллект поможет не только в рефакторинге, но и в реорганизации и упрощении архитектуры.
  • Независимость от фреймворков: UI-код будет опираться на структуру дизайна, а дизайн — на онтологии,. Это создаст гибкий конвейер, где изменения легко распространяются по всей системе.

Представьте ситуацию: дизайнер обновляет макет в Figma и обнаруживает несоответствие с текущей онтологией. В этом случае искусственный интеллект помогает минимизировать изменения как в общей онтологии, так и в самом макете, предлагая конкретные корректировки и указывая, где дизайн можно упростить. После утверждения макета AI автоматически рефакторит или добавляет необходимые компоненты, а бэкенд адаптируется к новым сущностям. Такой конвейер мог бы сделать процесс разработки более целостным и независимым от конкретных технологий.

Заключение

Долгосрочное планирование в UI-разработке уже не заключается в попытке предсказать будущее технологий. Речь идет о том, чтобы создать систему, которая готова меняться. Отделяя логику приложения от конкретных деталей реализации и используя формализованные онтологии, мы сможем выйти из замкнутого круга постоянных обновлений и переделок.

Давайте перестанем писать код, ориентируясь исключительно на текущий тренд (React, Angular и т.д.), и начнем создавать гибкие, легко адаптируемые системы, которые выдержат испытание временем. Например будем использовать инъекцию зависимостей, а также MVVM архитектуру и изолированый вью.

В будущем UI-разработка станет больше похожа на философию в действии с элементами DevOps — а это, по сути, про понимание и интеграцию всех деталей вместе. Позволю себе вольность и скажу, что вероятно UI разработчик будет больше как "DesignOps", больше похожим на дизайнера на стеройдах. Но дизайнер в моем понимании будет больше похож на архитектора, чьей задачей является problem solving через дизайн, а не простое создание красивых макетов.

Надеюсь, эта статья была полезной и помогла вам взглянуть на разработку интерфейсов с новой стороны и ментально подготовиться к переменам, которые уже не за горами.

  • Article Theme: The moment the change occurs

  • Article Goal: To explain why long-term planning fails in the current status-quo of UI development, how to fix it and to give a prediction of what the future of UI development might look like.

  • Article Topic: How to plan long-term in UI development with the current state of affairs

    • Core Issue: Modern development is tightly bound to implementation details and frameworks
      • Few approaches exist to abstract away these details.
      • Details change over time, turning code into legacy when tied directly to them.
      • Complete separation from these details might be impossible, but we need mechanisms for flexible adaptation.
      • Role of Tools like TypeScript
        • Provides rapid feedback on correctness.
        • Supports AI-driven code refactoring.
  • Proposed Solutions

    • 1. Architectural Approach

      • Rethinking Application Architecture
        • Apps should be thought of in architectural terms rather than just concrete implementations.
        • Many equate architecture with specific frameworks (e.g., Angular’s MVVM pattern), but true architecture is more abstract.
      • Key Architectural Principles
        • Postponing Decisions: Delay binding to specific implementations or frameworks.
        • Separation of Concerns: Isolate different parts of the application for easier testing and maintenance.
        • Facilitation of Testing: Architecture should make testing straightforward.
      • Implementation Suggestions
        • MVVM can be implemented without relying on classes or specific frameworks.
        • Propose a component-based MVVM where the view model is essentially a props object.
        • Combining MVVM with dependency injection can meet the criteria of flexibility and decoupling.
      • Example
        • Enhanced TicTacToe tutorial as made famous by React
      • This approach isn't tied to any specific framework or even platform or programming language, this is the power of being able to isolate from details.
    • 2. Conceptualization and Top-Down Approach

      • Current Trends Impacting Development
        • AI trends and the move toward declarative programming hide many implementation details.
        • This shift poses challenges because not everyone can easily conceptualize ideas in a declarative manner.
      • The Challenge of Conceptualization
        • Conceptualization involves the creation of ontologies for concepts and entities.
        • It is a philosophical process similar to what Domain-Driven Design (DDD) attempts.
        • Based on our understanding of the world or our job we construct ontologies. The better our understanding of something the more accurate our ontology is and the better it scales, as lower level concepts could always be replaced with higher level concepts
        • Issues with DDD
          • Often suffers from leaky abstractions.
          • Ties universal language too closely to the development process
            • Ubiquitous language also could be created for non-dev contexts such as law firms, schools, etc.
      • Goals for a Better Approach
        • There should be tools that allow to construct and refactor ontologies easily while allowing those changes to propagate throughout the company naturally and allowing all the dependent parts of the system to be updated automatically.
        • Simplify the system while ensuring all necessary components are present.
        • Avoid pre-planning or unnecessary multiplication of entities.
      • Top-Down Methodology
        • Step-by-Step Process
          • Start with mocks of a page performing a specific task.
          • Progress to detailed designs.
          • Finally, build the frontend.
        • Benefits
          • Necessary entities and relationships emerge naturally during the process.
          • Acts like Occam's razor by favoring simplicity.
        • Automation of Ontologies
          • Ensure ontologies are automated for correctness.
          • Enable AI to refactor ontologies on demand.
          • Allow changes to be easily propagated to all dependent parts of the system.
      • Example
        • Owl ontology language with AI-driven refactoring and feedback on correctness and suggestions for simplification
  • Predictions

    • Future of Technology
      • As Henric Altschuller famously said that there is a law of technical systems increasing the degree of their idealism over time, this tred is really going to continue, software will keep getting more and more declarative
    • Future of UI Development
      • The future of UI development will be more like a combination of an applied philosopher and a devops role, where we have to be able to integrate all the details together.
    • Future of Tools
      • We will need tools that can automatically generate designs from ontology and the code from designs.
      • Ontology is the single source of truth for the application. Designs know about ontology but not UI . UI code only knows about design structure and IO interfaces. Backend only knows about consumer interfaces.
  • Conclusion

    • We need to implement a way to automatically generate the ontology from the code and create declarative tooling that would produce the necessary code and details combining ontology, designs, UI and backend in that order of priority in the sequence of detail generation. In essence, app development in future becomes more like a combination of an applied philosopher and a devops role, where we have to be able to integrate all the details together.
import { React, useState } from "./libs/react";
// This application's architecture is guided by the following principles:
// - **Component-based MVVM**: Uses ViewModels (props objects) to abstract view details, allowing components to focus solely on presentation.
// - **Dependency Injection**: Enables testing of components in isolation by injecting dependencies, facilitating easier maintenance and scalability.
// - **Separation of Concerns**: Distinguishes between view, logic, and IO, ensuring each aspect is managed independently.
// - **Single Source of Truth**: Maintains a centralized and minimal state that fully represents the application's current status.
// - **Ubiquitous Language through Components**: Treats components as the core domain entities, developed top-down to adhere to Occam's Razor—favoring the simplest effective solutions.
// - **Code Relationships over File Structure**: Emphasizes the interactions and relationships between code components rather than relying solely on file organization.
// ------------------------------------------------------------------------------------------------
// Entry point for the article
// ------------------------------------------------------------------------------------------------
export type TState = {
squares: string[]; // :Square :parent :Entity .
xIsNext: boolean;
history: string[][];
};
// Root component that manages game state and event handling
export default function App() {
// Initialize game state with empty board
const [state, setState] = useState<TState>({
squares: Array(9).fill(""),
xIsNext: true,
history: [Array(9).fill("")],
});
// Render game board and move history
return (
<Game
boardProps={mapStateToBoardProps(state, setState)}
moveProps={mapMovesToNavigateToMoveButtonProps(state, setState)}
/>
);
}
// ------------------------------------------------------------------------------------------------
// Game Component
// ------------------------------------------------------------------------------------------------
// Main game component that displays the board and move history
// :Game :parent :Entity .
export function Game({
boardProps,
moveProps,
}: {
boardProps: TBoardProps;
moveProps: TNavigateToMoveButtonProps[];
}) {
return (
<div className="game">
<div className="game-board">
<Board {...boardProps} />
</div>
<div className="game-info">
<ol>
{moveProps.map((moveProps, index) => (
<NavigateToMoveButton key={index} {...moveProps} />
))}
</ol>
</div>
</div>
);
}
// ------------------------------------------------------------------------------------------------
// Board Component
// ------------------------------------------------------------------------------------------------
export type TBoardProps = {
squareProps: TSquareProps[][];
status: string;
};
// :Board :parent :Entity .
// Board component that displays the game board and status
export function Board({ squareProps, status }: TBoardProps) {
return (
<>
<div className="status">{status}</div>
{squareProps.map((row, rowIndex) => (
<div className="board-row" key={rowIndex}>
{row.map((square, colIndex) => (
<Square
key={`${rowIndex}-${colIndex}`}
value={square.value}
onSquareClick={square.onSquareClick}
index={square.index}
/>
))}
</div>
))}
</>
);
}
// Maps game state to board component props
export const mapStateToBoardProps = (
state: TState,
setState: (state: TState) => void
): TBoardProps => {
// Check if there's a winner
const winner = calculateWinner(state.squares);
let status;
// Set status message based on game state
if (winner) {
status = "Winner: " + winner;
} else {
status = "Next player: " + (state.xIsNext ? "X" : "O");
}
// Return props for board component
return {
squareProps: mapStateToSquareProps(state, setState), // Props for individual squares
status, // Game status message
};
};
// ------------------------------------------------------------------------------------------------
// Square Component
// ------------------------------------------------------------------------------------------------
export type TSquareProps = {
value: string;
index: number;
onSquareClick: (index: number) => void;
};
// :Square :parent :Entity .
// Square component that displays a single square on the game board
export function Square({ value, onSquareClick, index }: TSquareProps) {
return (
<button className="square" onClick={() => onSquareClick(index)}>
{value}
</button>
);
}
// Handles a player making a move at position i
export function makeMove(
i: number,
state: TState,
setState: (state: TState) => void
) {
// Return early if game is won or square is already filled
if (calculateWinner(state.squares) || state.squares[i]) {
return;
}
// Create copy of squares array and mark the move
const nextSquares = state.squares.slice();
if (state.xIsNext) {
nextSquares[i] = "X";
} else {
nextSquares[i] = "O";
}
// Update game state with new move
setState({
squares: nextSquares,
xIsNext: !state.xIsNext,
history: [...state.history, nextSquares],
});
}
// Converts game state into props for Square components arranged in rows
export const getMapStateToSquareProps =
({
someAsyncDependency, // this dependency illustrates how IO can be injected into the component
}: {
someAsyncDependency: () => Promise<void>;
}) =>
(state: TState, setState: (state: TState) => void): TSquareProps[][] => {
// Group squares into rows of 3
return state.squares.reduce((rows, square, index) => {
if (index % 3 === 0) rows.push([]);
rows[rows.length - 1].push({
value: square,
onSquareClick: () => {
someAsyncDependency()
.then(() => {
makeMove(index, state, setState);
})
.catch((e) => {
console.error(e);
});
},
index,
});
return rows;
}, [] as TSquareProps[][]);
};
const mapStateToSquareProps = getMapStateToSquareProps({
someAsyncDependency: async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
alert("Async dependency has been resolved");
},
});
// ------------------------------------------------------------------------------------------------
// NavigateToMoveButton Component
// ------------------------------------------------------------------------------------------------
export type TNavigateToMoveButtonProps = {
onClick: () => void;
children: string;
};
// :Move :parent :Entity .
// component that displays a button that navigates to a specific move in the game
export function NavigateToMoveButton({
onClick,
children,
}: TNavigateToMoveButtonProps) {
return (
<li>
<button onClick={onClick}>{children}</button>
</li>
);
}
// Maps game history to move navigation buttons
export const mapMovesToNavigateToMoveButtonProps = (
state: TState,
setState: (state: TState) => void
): TNavigateToMoveButtonProps[] => {
// Convert each historical game state into a move button
return state.history.map((squares, move) => {
const description = [
move === 0 && "Go to game start",
move > 0 && `Go to move #${move}`,
].filter(Boolean)[0] as string;
return {
// When clicked, restore game state to this move
onClick: () => {
setState({
squares: state.history[move], // Board state at this move
xIsNext: move % 2 === 0, // Determine whose turn is next
history: state.history.slice(0, move + 1), // Trim future moves
});
},
children: description,
};
});
};
// ------------------------------------------------------------------------------------------------
// Misc
// ------------------------------------------------------------------------------------------------
/**
* Determines the winner of a tic-tac-toe game.
*/
export function calculateWinner(squares: TState["squares"]) {
// Possible winning combinations
const lines = [
[0, 1, 2], // Top row
[3, 4, 5], // Middle row
[6, 7, 8], // Bottom row
[0, 3, 6], // Left column
[1, 4, 7], // Middle column
[2, 5, 8], // Right column
[0, 4, 8], // Diagonal from top-left to bottom-right
[2, 4, 6], // Diagonal from top-right to bottom-left
];
// Check each winning combination
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
// If the squares at positions a, b, and c are the same and not null, we have a winner
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a]; // Return the winner ("X" or "O")
}
}
// If no winner is found, return null
return null;
}
@prefix : <urn:webprotege:ontology:tictactoe#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
:Move :parent :Action .
:SquareTopic :parent :GameTopic .
:Game :parent :GameTopic .
:Game :parent :Entity .
:Square :parent :Entity .
:Square :parent :SquareTopic .
:Mark :parent :Entity .
:Mark :parent :SquareTopic .
:Player :parent :Entity .
:Player :parent :PlayerTopic .
:PlayerTopic :parent :GameTopic .
:Board :parent :Entity .
:Board :parent :BoardTopic .
:BoardTopic :parent :GameTopic .
:Move :parent :SquareTopic .
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment