Skip to content

Michael4d45/tic-tac-toe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tic-Tac-Toe Game

A modern Tic-Tac-Toe game built with Godot 4, featuring an AI opponent and shader-based rendering for crisp graphics.

Play Online: https://tic-tac-toe.michael4d45.com/

Netlify Status

Project Structure

The project is organized as a Godot 4 game with the following key components:

src/
├── Game.tscn              # Main game scene
├── gameboard.gd           # Core game logic and AI
├── gameboard.gdshader     # 2D shader for rendering game board
├── background.gdshader    # Animated background shader
├── AI.gd                  # UI controls for AI/Human selection
├── Enums.gd              # Game state and player type enums
├── RichTextLabel.gd      # Game status display logic
└── project.godot         # Godot project configuration

Core Components

  • gameboard.gd: Contains the main game logic, AI implementation, win detection, and coordinate conversion between mouse clicks and grid positions
  • gameboard.gdshader: 2D GLSL shader that renders the grid lines, X symbols, and O symbols directly on the GPU
  • AI.gd: Manages the UI controls for switching between human and AI players
  • Enums.gd: Defines game states (X_TURN, O_TURN, X_WON, O_WON, TIE) and player types (HUMAN, AI)

AI Implementation

The AI uses a simple but effective random placement strategy:

Algorithm Details

  1. Random Selection: The AI randomly selects from all available (empty) grid positions
  2. Bitwise Masking: Uses bitwise operations for efficient position tracking:
    • Each grid position is represented as a bit in an integer mask
    • o_mask tracks O player positions, x_mask tracks X player positions
    • Combined mask (o_mask | x_mask) identifies occupied positions

Key AI Functions

func ai():
    # Get current board state from shader parameters
    var grid_size = material.get_shader_parameter("grid_size")
    var o_mask = material.get_shader_parameter("o_mask")
    var x_mask = material.get_shader_parameter("x_mask")
    
    # Find random empty position
    var random_placement = get_random(o_mask | x_mask, int(grid_size * grid_size))
    
    # Make move and update game state
    if game_state == Enums.GameState.X_TURN:
        x_mask |= random_placement
    elif game_state == Enums.GameState.O_TURN:
        o_mask |= random_placement
    
    process_game(x_mask, o_mask, grid_size)

The get_random() function recursively selects positions until it finds an unoccupied spot, ensuring valid moves.

2D Shader Rendering System

The game uses Godot's 2D shaders to render everything directly on the GPU, providing smooth, scalable graphics regardless of grid size.

Shader Architecture

File: gameboard.gdshader

The shader system works by:

  1. Mathematical Grid Generation: Creates grid lines using modulo operations
  2. Symbolic Rendering: Draws X's and O's using mathematical functions
  3. Bitwise Position Checking: Uses integer masks to determine what to draw where

Grid Line Rendering

float horizontal_line = abs(mod((UV.y + offset) * grid_size, 1.0)) < line_thickness ? 1.0 : 0.0;
float vertical_line = abs(mod((UV.x + offset) * grid_size, 1.0)) < line_thickness ? 1.0 : 0.0;
  • Uses UV coordinates and modulo operations to create evenly spaced lines
  • grid_size parameter allows for dynamic grid sizes (3x3, 5x5, etc.)
  • Line thickness is adjustable via uniform parameters

Symbol Rendering

O Symbols (Circles)

bool is_circle(vec2 uv, vec2 pos) {
    float circle_size = 0.00002518629 + (1.168574 - 0.00002518629)/(1.0 + pow((grid_size/0.4903008),2.213454));
    float circle_thickness = (37764.0)/(1.0 + pow((grid_size/0.0002911425),1.8));
    
    translate_pos(uv, pos);
    
    bool outer_circle = abs((pos.y * pos.y) + (pos.x * pos.x)) < circle_size;
    bool inner_circle = abs((pos.y * pos.y) + (pos.x * pos.x)) > circle_size - circle_thickness;
    
    return outer_circle && inner_circle;
}

X Symbols (Crosses)

bool is_cross(vec2 uv, vec2 pos) {
    translate_pos(uv, pos);
    float cross_thickness = 0.01;
    
    // Define bounds for the cross
    float bottom = -0.45 / grid_size;
    float top = 0.45 / grid_size;
    float left = -0.45 / grid_size;
    float right = 0.45 / grid_size;
    
    // Calculate diagonal lines using linear equations
    bool forward_slash = (pos.y + pos.x > -cross_thickness) && (pos.y + pos.x < cross_thickness);
    bool back_slash = (pos.y - pos.x > -cross_thickness) && (pos.y - pos.x < cross_thickness);
    
    return (forward_slash || back_slash) && in_bounds;
}

Position Masking System

The shader receives integer masks from the game logic and uses bitwise operations to determine which symbols to render:

bool check_pos_in_mask(vec2 pos, int marksMask) {
    int posMask = 1 << int(pos.y) * int(grid_size) + int(pos.x);
    return (marksMask & posMask) != 0;
}

This system allows for:

  • Efficient State Management: Board state stored as simple integers
  • Dynamic Grid Sizes: Works with any NxN grid (currently set to 5x5)
  • Scalable Rendering: Symbols automatically scale with grid size
  • GPU Performance: All rendering calculations happen on the GPU

Rendering Pipeline

  1. Fragment Shader Execution: For each pixel, the shader determines:

    • Is this pixel part of a grid line?
    • Is this pixel part of an O symbol in an occupied position?
    • Is this pixel part of an X symbol in an occupied position?
  2. Position Translation: Converts UV coordinates to grid positions

  3. Mask Checking: Checks if the current grid cell should contain a symbol

  4. Color Assignment: Assigns appropriate colors based on what should be rendered

Technology Stack

  • Engine: Godot 4.2
  • Languages: GDScript (game logic), GLSL (shaders)
  • Rendering: OpenGL ES 3.0 / WebGL 2.0 compatible
  • Deployment: Netlify (supports required headers for Godot 4 web export)

Running the Project

  1. Prerequisites: Install Godot 4.2 or later
  2. Open Project: Open src/project.godot in Godot
  3. Run Game: Press F5 or use the Play button
  4. Web Export: Use Godot's HTML5 export template for web deployment

Features

  • Variable Grid Size: Currently 5x5, easily configurable
  • AI vs Human: Toggle each player between AI and human control
  • Shader-Based Graphics: Smooth, scalable rendering
  • Automatic Game Reset: AI vs AI games reset automatically
  • Win Detection: Detects wins, ties, and game states
  • Responsive Design: Works on desktop and web browsers

Deployed on Netlify because GitHub Pages doesn't support the custom headers required for Godot 4 web rendering.

About

learning godot

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •