• בלוג
  • כזה ניסיתי: Opencode ומודלי קוד פתוח מ Ollama

כזה ניסיתי: Opencode ומודלי קוד פתוח מ Ollama

21/02/2026

המודלים הפתוחים מתקדמים כל הזמן ויחד איתם גם סוכני הקידוד שיודעים להפעיל אותם. סוכני הקידוד המרכזיים שעובדים עם מודלים חופשיים הם:

  1. Droid
  2. Opencode
  3. Kilocode
  4. Aider
  5. Cline
  6. Roo Code
  7. Claude Code

לכבוד הפוסט היום רציתי לנסות לכתוב משחק סנייק עם מודל קוד פתוח ולראות מה מצבם ואם הם מצליחים לעמוד במשימה. התוצאה הפתיעה אותי לטובה ומאז הניסוי עברתי לעבוד עם מודל פתוח גם בפרויקטים אמיתיים עם תוצאות מעולות. אבל תחילה פרטי הניסוי.

1. בואו נבנה משחק סנייק

התקנתי opencode ו ollama, נרשמתי לשירות הפרמיום של ollama שמאפשר להריץ שאילתות מול המודלים הפתוחים בענן שלהם (עשרים דולר בחודש. המחשב שלי לא מספיק טוב בשביל להריץ מודלים), והפעלתי את אופןקוד עם תיקיית בסיס של פרויקט next.js ריק והדבקתי את הפרומפט הבא:

Let's scaffold a math snake game
It's a regular snake game but whenever you eat an apple show a math exercise about multiplication table (multiply 2 numbers, user types
the result)
Create the project in next.js fullstack app with server components using multiple files and industry best practices
dev server is already running on http://localhost:3001/ with hot reloading so you can just change the code and check the result in the
browser

Plan the game, its UI and its tests and then let's code

בשביל להפעיל אופןקוד עם מודל קוד פתוח דרך ollama כותבים משורת הפקודה:

ollama launch opencode --model <model-name>

את הניסוי הרצתי עם שלושה מודלים:

  1. kimi-k2.5
  2. minimax-m2.5
  3. glm-5

התוצאות בגיטהאב בקישורים:

https://github.com/ynonp/math-snake-foss-models/tree/opencode-glm5

https://github.com/ynonp/math-snake-foss-models/tree/opencode-kimi

https://github.com/ynonp/math-snake-foss-models/tree/opencode-minimax

2. איך זה עבד

שלושת המודלים הצליחו ליצור את המשחק. קימי הצליח בנסיון הראשון ו glm ו minimax היו צריכים פרומפט המשך כי הגרסה הראשונה שלהם היתה עם שגיאה.

שלושת המודלים יצרו גם משחק וגם בדיקות. בכולם הבדיקות בדקו את הלוגיקה של המשחק בלי לבדוק UI. כל השלושה ניסו להפריד בין פונקציות הלוגיקה לקוד הממשק כדי שיוכלו לבדוק את הלוגיקה.

שלושת המודלים סיימו תוך דקות והגבלת הטוקנים של ollama מאוד נדיבה כך שנראה שגם אם הייתי מריץ סוכן קידוד מסביב לשעון לא היו נגמרות לי הבקשות.

מבחינת התוצאות הכי אהבתי את הקוד של minimax. הקוד פשוט, הנחש מצויר עם CSS Grid, אין כמעט שימוש ביכולות של ריאקט (אין hooks, יש שני אפקטים עבור לולאת המשחק וטיפול בקלט). והקוד מאוד קריא. לדוגמה זו לולאת המשחק:

const handleTick = useCallback(() => {
  setGameState(prev => tick(prev));
}, []);

שזה מעולה כיוון שכל הלוגיקה בפונקציית tick היא חיצונית לריאקט ואפשר בקלות לבדוק אותה.

קימי יצר תוכנית מצוינת אבל לקח כיוון מסורבל בקוד. במקום להשתמש ב div-ים הוא יצר canvas. זה לא סוף העולם אבל זה אומר שהבדיקות שלו הרבה פחות טובות כיוון שיש המון לוגיקה שקשורה לציור אותה הוא לא בדק. מבחינת ההבדלים בינו לבין minimax אז הנה דוגמה קטנה הקוד של קימי שבדק אם תשובה לתרגיל חשבון היא נכונה הוא:

  const handleMathSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const userAnswer = parseInt(answer, 10);
    const correct = userAnswer === gameState.currentMathProblem?.answer;

    if (correct) {
      setGameState((prev) => ({
        ...prev,
        snake: growSnake(prev.snake),
        apple: generateApple(prev.snake),
        score: prev.score + SCORE_PER_APPLE,
        speed: Math.max(MIN_SPEED, prev.speed - SPEED_DECREMENT),
        status: 'PLAYING',
        currentMathProblem: null,
      }));
    } else {
      const newLives = gameState.lives - 1;
      if (newLives <= 0) {
        setGameState((prev) => ({ ...prev, status: 'GAME_OVER', lives: 0 }));
      } else {
        setGameState((prev) => ({
          ...prev,
          snake: shrinkSnake(prev.snake),
          apple: generateApple(prev.snake),
          status: 'PLAYING',
          currentMathProblem: null,
          lives: newLives,
        }));
      }
    }
    setAnswer('');
  };

זו פונקציה שמוגדרת בתוך קומפוננטה שמפעילה setState מכמה מקומות שונים לפי הנסיבות. לי קשה לקרוא אותה או לעקוב אחריה ובטח לבדוק אותה.

מינימקס הפריד את זה ל-2, החלק בתוך הקומפוננטה הוא בסך הכל:

const handleAnswerSubmit = (answer: number) => {
  setGameState(prev => submitAnswer(prev, answer));
};

והלוגיקה בקובץ נפרד לא קשורה לריאקט ולכן אפשר בקלות לבדוק אותה:

export function submitAnswer(state: GameState, userAnswer: number): GameState {
  if (!state.mathProblem) return state;

  const isCorrect = userAnswer === state.mathProblem.answer;

  if (isCorrect) {
    const newSpeed = Math.max(MIN_SPEED, state.speed - SPEED_DECREASE);
    return {
      ...state,
      score: state.score + POINTS_PER_ANSWER,
      speed: newSpeed,
      isPaused: false,
      mathProblem: null,
      apple: generateApple(state.snake),
    };
  }

  const newLives = state.lives - 1;

  if (newLives <= 0) {
    return {
      ...state,
      lives: 0,
      isGameOver: true,
      isPaused: true,
      mathProblem: null,
    };
  }

  return {
    ...state,
    lives: newLives,
    isPaused: false,
    mathProblem: null,
    apple: generateApple(state.snake),
  };
}

המודל האחרון glm לקח גישה היברידית, הוא גם השתמש בגריד כמו מינימקס אבל אצלו שוב המשחק נכתב בתוך Hook ולכן היתה הרבה לוגיקה שלא נבדקה. גלם גם הוסיף טעינה דינמית של לוח המשחק מאיזושהי סיבה:

'use client';

import dynamic from 'next/dynamic';

const GameBoard = dynamic(
  () => import('@/components/Game/GameBoard').then((mod) => mod.GameBoard),
  { ssr: false }
);

export function Game() {
  return <GameBoard />;
}

ובדוגמת בדיקת התרגיל הוא כתב:

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    const userAnswer = parseInt(answer, 10);

    if (isNaN(userAnswer)) {
      setError(true);
      return;
    }

    if (userAnswer === problem.answer) {
      onCorrect();
    } else {
      onWrong();
    }
  };

  const handleChange = (value: string) => {
    setAnswer(value);
    setError(false);
  };


// in hook file
  const handleCorrectAnswer = useCallback(() => {
    setCurrentProblem(null);
    setGameState('PLAYING');
  }, []);

  const handleWrongAnswer = useCallback(() => {
    setLives((prev) => {
      const newLives = prev - 1;
      if (newLives <= 0) {
        setGameState('GAME_OVER');
      }
      return newLives;
    });
    setCurrentProblem(null);
    if (lives > 1) {
      setGameState('PLAYING');
    }
  }, [lives]);

שזה דוגמה נפלאה לקוד שנראה יפה אבל בעצם קשה להבנה כי יש לנו יותר מדי משתני סטייט ויותר מדי פונקציות מפוזרות בכמה קבצים בלי סיבה אמיתית להפרדה.

סך הכל בעקבות הניסוי אני מודה שהתאהבתי ב minimax התחלתי גם להשתמש בו ליצירת קוד בפרויקטים אמיתיים ואני מאוד מרוצה בינתיים.