PHPackages                             chemaclass/phel-connect4 - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. chemaclass/phel-connect4

ActiveProject[Utility &amp; Helpers](/categories/utility)

chemaclass/phel-connect4
========================

Two-player terminal Connect 4 in Phel, with an optional bitboard minimax AI.

10

Since Apr 18Pushed 1mo agoCompare

[ Source](https://github.com/Chemaclass/phel-connect4)[ Packagist](https://packagist.org/packages/chemaclass/phel-connect4)[ RSS](/packages/chemaclass-phel-connect4/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependenciesVersions (1)Used By (0)

Connect 4 in Phel
=================

[](#connect-4-in-phel)

Two-player terminal [Connect 4](https://en.wikipedia.org/wiki/Connect_Four) written in [Phel](https://phel-lang.org), a Lisp that compiles to PHP. Optional minimax AI opponent.

```
 1 2 3 4 5 6 7
|. . . . . . .|
|. . . . . . .|
|. . . . . . .|
|. . . . . . .|
|. . . O O O .|
|. . . X X X X|

Red (X) wins!

```

Install
-------

[](#install)

Requires PHP 8.3+ and Composer.

```
composer install
```

Run
---

[](#run)

Use the composer scripts:

```
composer play           # two players (default)
composer play:2p        # same as above, explicit
composer play:1p        # vs AI; you play Red (move first), AI plays Yellow
composer play:ai-first  # vs AI; AI plays Red (moves first), you play Yellow
```

Or run directly with env vars:

```
./vendor/bin/phel run src/phel/main.phel                       # 2 players
AI=yellow ./vendor/bin/phel run src/phel/main.phel             # AI = yellow
AI=red AI_DEPTH=6 ./vendor/bin/phel run src/phel/main.phel     # AI first, deeper search
```

How to play
-----------

[](#how-to-play)

- Red (`X`) moves first, then players alternate.
- Drop a token into any column that is not full; it falls to the lowest empty row.
- First player to line up **four in a row** (horizontal, vertical, or diagonal) wins.
- Board full with no winner → draw.

### Commands at the prompt

[](#commands-at-the-prompt)

InputAction`1`..`7`Drop token into that column`u`Undo the last move (replays history from scratch)`q` or `quit`Abort gameConfiguration
-------------

[](#configuration)

Environment variables:

VariableDefaultMeaning`AI`*(off)*`red` or `yellow` — which side the AI plays`AI_DEPTH``5`Minimax search depth. Higher = stronger and slower (`5` ≈ 2–7s, `6` ≈ 10s, `7` ≈ 35s per move)`COLOR`*(on)*Set to `0` to disable ANSI colors and screen clearingExample: strong AI, no colors, piped input.

```
COLOR=0 AI=yellow AI_DEPTH=4 ./vendor/bin/phel run src/phel/main.phel
```

Tests
-----

[](#tests)

```
./vendor/bin/phel test
```

Covers board logic, game state transitions, and AI tactics (forced win, forced block, center preference).

Project layout
--------------

[](#project-layout)

```
src/phel/
  board.phel    ; pure board ops: make/drop/winner?/valid-cols
  game.phel     ; TGame struct: step, undo, game-over?
  render.phel   ; ANSI rendering + display names
  ai.phel       ; negamax with alpha-beta pruning
  main.phel     ; CLI loop

tests/phel/
  board_test.phel
  game_test.phel
  ai_test.phel

```

AI notes
--------

[](#ai-notes)

- **Bitboard representation**: two 49-bit ints encode the position (columns laid out as 7 bits each, with a separator bit to make shift-based win detection safe).
- **Win detection**: for each axis shift (vertical 1, horizontal 7, diagonal 6, diagonal 8), `pos & (pos >> s) & m & (m >> 2s)` detects a 4-in-a-row in a handful of bitwise ops instead of scanning 69 windows.
- **Negamax + alpha-beta**: center-out move ordering so good candidates prune the wide branches early.
- **Heuristic**: per-axis 2-in-a-row and 3-in-a-row counts via `popcount`, plus a center-column bonus.
- **Depth-aware scoring**: terminal wins return `win-score + depth`, so the AI prefers winning faster and losing later.
- **Opening shortcut**: the first move (empty board) plays the center column without searching.

During the AI's turn you'll see a `thinking...` line while the search runs; when it returns, the move and elapsed time are printed before the next board render. Every turn also shows a `Last:  → column N` line so you can see what just happened after the screen clears.

###  Health Score

20

—

LowBetter than 13% of packages

Maintenance59

Moderate activity, may be stable

Popularity2

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/3d166420c6770c5941e10bd68b2d26501eabb432e280d8e6eba0a344bcc1e5ae?d=identicon)[Chemaclass](/maintainers/Chemaclass)

---

Top Contributors

[![Chemaclass](https://avatars.githubusercontent.com/u/5256287?v=4)](https://github.com/Chemaclass "Chemaclass (16 commits)")

---

Tags

lispphel-langphp

### Embed Badge

![Health badge](/badges/chemaclass-phel-connect4/health.svg)

```
[![Health](https://phpackages.com/badges/chemaclass-phel-connect4/health.svg)](https://phpackages.com/packages/chemaclass-phel-connect4)
```

###  Alternatives

[shrink0r/monatic

Fiddling with the monad concept in php

245.3k2](/packages/shrink0r-monatic)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
