Reversi est un jeu stratégique à deux joueurs. Le but est d’avoir plus de pions de sa couleur que l’adversaire à la fin de la partie.
Les 4 pions de départ sont placés au centre en diagonale : orange-bleu / bleu-orange.
| Boutons | Actions |
|---|---|
| ↑ ↓ ← → | Déplacement dans les quatre directions |
| Ⓐ | Poser le pion |
| Ⓑ | Afficher les possibilités |
| Démarre une partie | |
| Sélection du mode |
Attention : Chaque appui sur Ⓑ enlève un point.
Le joueur ayant le plus de pions de sa couleur à la fin gagne.
/ *******************************************************
*
* ██████╗ ███████╗██╗ ██╗███████╗██████╗ ███████╗██╗
* ██╔══██╗██╔════╝██║ ██║██╔════╝██╔══██╗██╔════╝██║
* ██████╔╝█████╗ ██║ ██║█████╗ ██████╔╝███████╗██║
* ██╔══██╗██╔══╝ ╚██╗ ██╔╝██╔══╝ ██╔══██╗╚════██║██║
* ██║ ██║███████╗ ╚████╔╝ ███████╗██║ ██║███████║██║
* ╚═╝ ╚═╝╚══════╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝
*
* -------------------------------------------------------
* REVERSI – Jeu NeoPixel (Matrix 16×16)
* -------------------------------------------------------
*
* Auteur : skuydi
* Année : 05/2026
* Plateforme : Arduino / compatible AVR
* Affichage : Matrice LED NeoPixel 16×16
* Commande : Manette NES (original)
* Bibliothèque : Adafruit_NeoPixel, TM1637Displayl, GameControllers
*
* -------------------------------------------------------
* DESCRIPTION
* -------------------------------------------------------
*
*
/ ********************************************************
#include <Adafruit_NeoPixel.h>
#include <TM1637Display.h>
#include "GameControllers.h"
// =========================================================
// CONFIG
// =========================================================
#define BRIGHTNESS 8 // 0..255
#define MATRIX_SERPENTINE 1 // 0 = linéaire, 1 = zigzag
#define ATTRACT_STEP_MS 260 // vitesse démo IA vs IA
#define AI_THINK_DELAY_MS 220 // vitesse IA en SOLO
#define FLIP_WHITE_MS 350 // flash blanc
#define FLIP_COLOR_MS 250 // retour couleur
#define SHOW_VALID_MOVES_BY_B 1 // B maintenu -> coups valides en jaune pulsé
// Pulse "additif" (évite que l'orange vire rouge)
#define CURSOR_PULSE_ADD_MIN 10
#define CURSOR_PULSE_ADD_MAX 40
#define CURSOR_PULSE_SPEED 0.004f
// =========================================================
// HARDWARE PINS (Nano Every / ATmega4809)
// =========================================================
#define PIN_NEOPIXEL 6
#define MATRIX_W 16
#define MATRIX_H 16
#define NUMPIXELS (MATRIX_W * MATRIX_H)
#define NES_LATCH 8
#define NES_CLOCK 9
#define NES_DATA 10
#define NES_INDEX 0
#define TM_P1_CLK 2 // Joueur 1 (orange)
#define TM_P1_DIO 3
#define TM_P2_CLK 11 // Joueur 2 (bleu)
#define TM_P2_DIO 12
Adafruit_NeoPixel strip(NUMPIXELS, PIN_NEOPIXEL, NEO_GRB + NEO_KHZ800);
TM1637Display displayP1(TM_P1_CLK, TM_P1_DIO);
TM1637Display displayP2(TM_P2_CLK, TM_P2_DIO);
GameControllers controllers;
// =========================================================
// STATES / MODES
// =========================================================
enum GameState { STATE_ATTRACT, STATE_MENU, STATE_PLAY, STATE_GAMEOVER };
enum GameMode { MODE_VS, MODE_SOLO }; // MENU: VS (1/1) default, SOLO (0/1)
static GameState gameState = STATE_ATTRACT;
static GameMode gameMode = MODE_VS;
// =========================================================
// REVERSI BOARD
// =========================================================
static int board[8][8]; // 0 empty, 1 P1 (orange), 2 P2 (bleu)
static int cursorX = 3, cursorY = 3;
static int currentPlayer = 1; // 1 or 2
static bool showMovesHeld = false; // B held
static int penaltyP1 = 0;
static int penaltyP2 = 0;
// =========================================================
// TIMERS
// =========================================================
static unsigned long lastAttractStep = 0;
static unsigned long lastAiAction = 0;
// pulse (yellow & cursor)
static unsigned long pulseTimer = 0;
static float pulseValue = 0.0f;
static bool pulseUp = true;
// D-pad repeat (smooth)
static bool moving = false;
static unsigned long moveStartTime = 0;
static unsigned long lastMoveTime = 0;
static const unsigned long firstRepeatDelay = 350;
static const unsigned long repeatRate = 90;
// =========================================================
// COLORS
// =========================================================
static uint32_t C_EMPTY, C_P1, C_P2, C_CURSOR_P1, C_CURSOR_P2, C_FLIP;
// 8 directions
static const int directions[8][2] = {
{ 1, 0},{-1, 0},{ 0, 1},{ 0,-1},
{ 1, 1},{-1,-1},{ 1,-1},{-1, 1}
};
// =========================================================
// UTILS
// =========================================================
static inline int clamp8(int v){ if(v<0) return 0; if(v>7) return 7; return v; }
static inline bool isOnBoard(int x,int y){ return x>=0 && x<8 && y>=0 && y<8; }
// =========================================================
// LED MAPPING
// =========================================================
int getLEDIndex(int x, int y){
#if MATRIX_SERPENTINE == 1
if ((y & 1) == 0) return y * MATRIX_W + x;
return y * MATRIX_W + (MATRIX_W - 1 - x);
#else
return y * MATRIX_W + x;
#endif
}
void drawCell(int cx, int cy, uint32_t color){
// board 8x8 -> 2x2 pixels on 16x16
for(int dy=0; dy<2; dy++){
for(int dx=0; dx<2; dx++){
int x = cx*2 + dx;
int y = cy*2 + dy;
strip.setPixelColor(getLEDIndex(x,y), color);
}
}
}
// =========================================================
// PULSE
// =========================================================
void updatePulse(){
if(millis() - pulseTimer > 30){
pulseTimer = millis();
if(pulseUp) pulseValue += 0.05f;
else pulseValue -= 0.05f;
if(pulseValue >= 1.0f){ pulseValue = 1.0f; pulseUp = false; }
if(pulseValue <= 0.0f){ pulseValue = 0.0f; pulseUp = true; }
}
}
// Jaune pulsé (clair) pour les coups possibles
uint32_t getPulseYellow(){
int base = 120;
int range = 135;
int intensity = base + (int)(pulseValue * range); // 120..255
return strip.Color(intensity, intensity, 0);
}
// Pulse ADDITIF: conserve la teinte (évite orange -> rouge)
uint32_t pulseAdditive(uint32_t baseColor){
float t = (float)millis() * CURSOR_PULSE_SPEED;
float s = (sinf(t) * 0.5f + 0.5f); // 0..1
int add = CURSOR_PULSE_ADD_MIN + (int)((CURSOR_PULSE_ADD_MAX - CURSOR_PULSE_ADD_MIN) * s);
uint8_t r = (baseColor >> 16) & 0xFF;
uint8_t g = (baseColor >> 8) & 0xFF;
uint8_t b = (baseColor ) & 0xFF;
r = (uint8_t)min(255, (int)r + add);
g = (uint8_t)min(255, (int)g + add);
b = (uint8_t)min(255, (int)b + add);
return strip.Color(r,g,b);
}
// =========================================================
// REVERSI RULES
// =========================================================
bool isValidMoveForPlayer(int x,int y,int player){
if(!isOnBoard(x,y)) return false;
if(board[y][x] != 0) return false;
int opp = (player==1)?2:1;
for(int d=0; d<8; d++){
int dx = directions[d][0];
int dy = directions[d][1];
int nx = x + dx;
int ny = y + dy;
bool foundOpp = false;
while(isOnBoard(nx,ny) && board[ny][nx] == opp){
nx += dx; ny += dy;
foundOpp = true;
}
if(foundOpp && isOnBoard(nx,ny) && board[ny][nx] == player) return true;
}
return false;
}
bool playerHasMove(int player){
for(int y=0;y<8;y++)
for(int x=0;x<8;x++)
if(isValidMoveForPlayer(x,y,player)) return true;
return false;
}
bool anyMoveExists(){
return playerHasMove(1) || playerHasMove(2);
}
// =========================================================
// SCORES
// =========================================================
void computeCounts(int &p1,int &p2){
p1=0; p2=0;
for(int y=0;y<8;y++){
for(int x=0;x<8;x++){
if(board[y][x]==1) p1++;
else if(board[y][x]==2) p2++;
}
}
}
void showScoresOnTM(){
int p1,p2; computeCounts(p1,p2);
int p1Disp = p1 - penaltyP1;
int p2Disp = p2 - penaltyP2;
if(p1Disp < 0) p1Disp = 0;
if(p2Disp < 0) p2Disp = 0;
displayP1.showNumberDec(p1Disp, false);
displayP2.showNumberDec(p2Disp, false);
}
void showMenuOnTM(){
// default = MODE_VS -> 1 / 1
if(gameMode == MODE_VS){
displayP1.showNumberDec(1, false);
displayP2.showNumberDec(1, false);
} else {
// SOLO: 0 / 1 (IA / humain)
displayP1.showNumberDec(0, false);
displayP2.showNumberDec(1, false);
}
}
// =========================================================
// FLIP ANIMATION
// =========================================================
void animateFlipCell(int x,int y,int player){
drawCell(x,y, C_FLIP);
strip.show();
delay(FLIP_WHITE_MS);
drawCell(x,y, (player==1)?C_P1:C_P2);
strip.show();
delay(FLIP_COLOR_MS);
}
void flipPiecesAnimated(int x,int y,int player){
int opp = (player==1)?2:1;
for(int d=0; d<8; d++){
int dx = directions[d][0];
int dy = directions[d][1];
int nx = x + dx;
int ny = y + dy;
int path[8][2];
int count = 0;
while(isOnBoard(nx,ny) && board[ny][nx] == opp){
if(count < 8){
path[count][0] = nx;
path[count][1] = ny;
}
count++;
nx += dx; ny += dy;
if(count > 7) break; // safety
}
if(count > 0 && isOnBoard(nx,ny) && board[ny][nx] == player){
int flips = (count > 8) ? 8 : count;
for(int i=0;i demo varies)
// =========================================================
int countFlipsForMove(int x,int y,int player){
int opp = (player==1)?2:1;
int total = 0;
for(int d=0; d<8; d++){
int dx = directions[d][0];
int dy = directions[d][1];
int nx = x + dx;
int ny = y + dy;
int count = 0;
while(isOnBoard(nx,ny) && board[ny][nx] == opp){
nx += dx; ny += dy;
count++;
}
if(count > 0 && isOnBoard(nx,ny) && board[ny][nx] == player){
total += count;
}
}
return total;
}
int evalMoveAI(int x,int y,int player){
int score = 0;
// corners
if((x==0&&y==0)||(x==7&&y==0)||(x==0&&y==7)||(x==7&&y==7)) score += 1000;
// X-squares (danger)
if((x==1&&y==1)||(x==6&&y==1)||(x==1&&y==6)||(x==6&&y==6)) score -= 80;
// edges
if(x==0||x==7||y==0||y==7) score += 60;
// flips
score += countFlipsForMove(x,y,player) * 5;
return score;
}
bool findBestMoveAI(int player,int &bx,int &by){
int bestScore = -999999;
int candX[64];
int candY[64];
int candCount = 0;
for(int y=0;y<8;y++){
for(int x=0;x<8;x++){
if(isValidMoveForPlayer(x,y,player)){
int s = evalMoveAI(x,y,player);
if(s > bestScore){
bestScore = s;
candCount = 0;
candX[candCount] = x;
candY[candCount] = y;
candCount++;
} else if(s == bestScore){
candX[candCount] = x;
candY[candCount] = y;
candCount++;
}
}
}
}
if(candCount <= 0) return false;
int pick = random(candCount);
bx = candX[pick];
by = candY[pick];
return true;
}
void doAITurn(int aiPlayer){
if(!playerHasMove(aiPlayer)){
// pass-turn
int other = (aiPlayer==1)?2:1;
if(playerHasMove(other)) currentPlayer = other;
else gameState = STATE_GAMEOVER;
return;
}
int bx,by;
if(findBestMoveAI(aiPlayer,bx,by)){
attemptMove(bx,by);
} else {
int other = (aiPlayer==1)?2:1;
if(playerHasMove(other)) currentPlayer = other;
else gameState = STATE_GAMEOVER;
}
}
// =========================================================
// INPUT (NES)
// =========================================================
void handleDpadRepeat(){
bool left = controllers.down(NES_INDEX,GameControllers::UP);
bool right = controllers.down(NES_INDEX,GameControllers::DOWN);
bool up = controllers.down(NES_INDEX,GameControllers::LEFT);
bool down = controllers.down(NES_INDEX,GameControllers::RIGHT);
unsigned long now = millis();
if(up||down||left||right){
if(!moving){
moving = true;
moveStartTime = now;
lastMoveTime = now;
if(up) cursorY = clamp8(cursorY - 1);
if(down) cursorY = clamp8(cursorY + 1);
if(left) cursorX = clamp8(cursorX - 1);
if(right) cursorX = clamp8(cursorX + 1);
} else if((now - moveStartTime > firstRepeatDelay) && (now - lastMoveTime > repeatRate)) {
if(up) cursorY = clamp8(cursorY - 1);
if(down) cursorY = clamp8(cursorY + 1);
if(left) cursorX = clamp8(cursorX - 1);
if(right) cursorX = clamp8(cursorX + 1);
lastMoveTime = now;
}
} else {
moving = false;
}
}
// =========================================================
// GAME OVER SCREEN
// =========================================================
void showWinnerScreen(){
static bool doneAnim = false;
int p1,p2; computeCounts(p1,p2);
int p1Disp = p1 - penaltyP1; if(p1Disp<0) p1Disp=0;
int p2Disp = p2 - penaltyP2; if(p2Disp<0) p2Disp=0;
displayP1.showNumberDec(p1Disp, false);
displayP2.showNumberDec(p2Disp, false);
uint32_t winColor;
if(p1Disp > p2Disp) winColor = C_P1;
else if(p2Disp > p1Disp) winColor = C_P2;
else winColor = strip.Color(255,255,255);
if(!doneAnim){
for(int i=0;i start game
if(controllers.pressed(NES_INDEX, GameControllers::A) ||
controllers.pressed(NES_INDEX, GameControllers::START))
{
resetBoardToStart();
gameState = STATE_PLAY;
drawBoard(true);
return;
}
drawBoard(false);
}
void runPlay(){
// START -> back to menu
if(controllers.pressed(NES_INDEX, GameControllers::START)){
gameState = STATE_MENU;
showMenuOnTM();
return;
}
// B held -> show valid moves
showMovesHeld = controllers.down(NES_INDEX, GameControllers::B);
// B pressed -> penalty -1
if(controllers.pressed(NES_INDEX, GameControllers::B)){
if(currentPlayer == 1) penaltyP1++;
else penaltyP2++;
showScoresOnTM();
}
// SOLO: human=P1 (1), AI=P2 (2)
if(gameMode == MODE_SOLO && currentPlayer == 2){
unsigned long now = millis();
if(now - lastAiAction >= (unsigned long)AI_THINK_DELAY_MS){
lastAiAction = now;
doAITurn(2);
}
drawBoard(false);
return;
}
// human turn
handleDpadRepeat();
if(controllers.pressed(NES_INDEX, GameControllers::A)){
attemptMove(cursorX, cursorY);
}
if(!anyMoveExists()){
gameState = STATE_GAMEOVER;
}
drawBoard(true);
}
void runGameOver(){
showWinnerScreen();
}
// =========================================================
// SETUP / LOOP
// =========================================================
void setup(){
strip.begin();
strip.setBrightness(BRIGHTNESS);
strip.show();
displayP1.setBrightness(3);
displayP2.setBrightness(3);
controllers.init(NES_LATCH, NES_CLOCK);
controllers.setController(NES_INDEX, GameControllers::NES, NES_DATA);
C_EMPTY = strip.Color(0,0,0);
C_P1 = strip.Color(255,50,0); //(orange)
C_P2 = strip.Color(0,150,200); // (bleu)
C_CURSOR_P1 = strip.Color(255, 50, 0);
C_CURSOR_P2 = strip.Color(0, 150, 200);
C_FLIP = strip.Color(255,255,255);
// randomness for demo variability
randomSeed(analogRead(A0));
// default menu mode = VS (1/1)
gameMode = MODE_VS;
resetBoardToStart();
currentPlayer = 1;
gameState = STATE_ATTRACT;
lastAttractStep = millis();
lastAiAction = millis();
showMenuOnTM();
}
void loop(){
updatePulse();
controllers.poll();
switch(gameState){
case STATE_ATTRACT: runAttract(); break;
case STATE_MENU: runMenu(); break;
case STATE_PLAY: runPlay(); break;
case STATE_GAMEOVER: runGameOver(); break;
}
}