とりあえず作る!ミニコデで始めるスマホでプログラミング

当ページのリンクには広告が含まれています。
  • URLをコピーしました!

昔プログラミングをかじったけど、難しくて挫折したんだよなぁ…

プログラムってハードルが高い印象ありますよね。かく言う当方も何度も挫折した人間です。

でも、時代が変わって、生成AIの登場で開発がだれでもできる時代になりました。

いのっち

めっちゃかんたんにプログラミングができる!みんなにも開発体験してほしい!

でも、プログラミングって結局パソコンがないとダメなんでしょ?

いのっち

そんなことないですよ。実はスマホさえあればプログラミングできちゃうんです。

その問題を解決するために、当方はスマホとAIだけですぐにプログラムを作って動かせる「ミニコデ」というWebアプリを開発しました。

ということで、ここからは「理屈抜き」で、プログラミングを体験してもらいます。

早速、下のボタンをタップしてください。ブラウザの別タブにミニコデが起動するので、その状態で記事を読み進めてください。

\ 今すぐタップ /

これ、本サイトをご覧になる上で基本動作です!覚えておいてください!

目次

すぐに試せるリバーシを作ってみる

まずはChatGPTやGemini、ClaudeでもいいのでまずAIを起動します。補足ですが、AIを使ったことがない方にはソースも用意してます。

AIを起動したら、さっそくプログラムを作ってもらいましょう。

まず、ミニコデで実行しやすいように、以下のようにプロンプトを書いてください。

スマホで動作する人対CPUのリバーシをHTML、CSS,JavaScriptで作成してください。ソースはHTMLに一つにまとめてください。

以下はChatGPTの例ですが、出力されたプログラムの右上にコードをコピーするというリンクがあるのでタップして、コピーしてください。

コードをコピーするをタップ

生成AIを使っていない方はリバーシのサンプルソースコードを用意したので、お使いください。

リバーシのソースコードはこちら

①下図の右、赤枠の部分をクリックするとソースを全部コピーできます。

②以下、ソースコードです。コピーしてください。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>リバーシ(人 vs CPU)</title>
<style>
  :root{
    --bg:#0b1020;
    --board:#0d5c34;
    --board-dark:#0a4b2a;
    --cell:#0f6a3c;
    --cell-alt:#0c5b33;
    --accent:#66e0a3;
    --text:#f6f7fb;
    --muted:#aab3c2;
    --danger:#ff6b6b;
    --shadow: 0 10px 25px rgba(0,0,0,.25);
    --radius: 18px;
  }
  html,body{margin:0;background:radial-gradient(1200px 600px at 70% -10%, #16213e 0%, #0e152b 45%, var(--bg) 100%);color:var(--text);font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Hiragino Kaku Gothic ProN", "Noto Sans JP", "Yu Gothic", "Helvetica Neue", Arial, "Apple Color Emoji","Segoe UI Emoji";}
  .wrap{max-width:920px;margin:clamp(8px,2vw,24px) auto;padding:clamp(8px,2vw,20px);}
  header{display:flex;gap:12px;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap}
  h1{font-size:clamp(20px,3.8vw,28px);margin:0;letter-spacing:.02em}
  .sub{font-size:12px;color:var(--muted)}
  .panel{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
  .badge{background:rgba(255,255,255,.06);padding:8px 10px;border-radius:999px;display:flex;gap:10px;align-items:center;box-shadow:var(--shadow)}
  .dot{width:14px;height:14px;border-radius:50%}
  .dot.black{background:#0b0b0b;box-shadow: inset 0 2px 0 rgba(255,255,255,.15), 0 0 0 2px rgba(255,255,255,.06)}
  .dot.white{background:#fafafa;box-shadow: inset 0 2px 0 rgba(255,255,255,.8), 0 0 0 2px rgba(0,0,0,.06)}
  .score{display:flex;gap:10px;align-items:center}
  .score span{font-variant-numeric: tabular-nums}
  .controls{display:flex;gap:8px;flex-wrap:wrap}
  button, select{
    background: linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.04));
    color:var(--text);border:1px solid rgba(255,255,255,.12);
    padding:10px 14px;border-radius:12px;cursor:pointer;
    font-weight:600;letter-spacing:.02em;
    box-shadow:var(--shadow);backdrop-filter: blur(6px);
  }
  button:disabled{opacity:.5;cursor:not-allowed}
  button.ghost{background:transparent;border-color:rgba(255,255,255,.18)}
  button.danger{border-color:rgba(255,0,0,.25)}
  .grid{
    width:min(92vw, 720px);
    aspect-ratio:1/1;
    margin:12px auto;
    background:radial-gradient(600px 300px at 10% -10%, #147a45, var(--board)) no-repeat;
    border-radius:var(--radius);
    padding:10px;
    box-shadow: var(--shadow), inset 0 0 0 1px rgba(255,255,255,.06);
    display:grid;
    grid-template-columns: repeat(8, 1fr);
    grid-template-rows: repeat(8, 1fr);
    gap:6px;
    touch-action: manipulation;
  }
  .cell{
    background:linear-gradient(180deg, var(--cell), var(--board-dark));
    border-radius:10px;
    position:relative;
    box-shadow: inset 0 2px 0 rgba(255,255,255,.08), inset 0 -2px 0 rgba(0,0,0,.2);
    user-select:none;
  }
  .cell:nth-child(odd){ background: linear-gradient(180deg, var(--cell-alt), var(--board-dark)); }
  .cell.valid::after{
    content:"";
    position:absolute;inset:8px;border-radius:50%;
    border:2px dashed rgba(255,255,255,.35);
    opacity:.75;pointer-events:none;
    animation: pulse 1.2s infinite ease-in-out;
  }
  .cell.hint::after{
    border:0;background:radial-gradient(circle at 50% 55%, rgba(255,255,255,.25), rgba(255,255,255,.05) 60%, transparent 61%);
  }
  @keyframes pulse{0%,100%{opacity:.35}50%{opacity:.8}}
  .disc{
    position:absolute;inset:10%;
    border-radius:50%;
    box-shadow: inset 0 8px 0 rgba(255,255,255,.18), 0 6px 12px rgba(0,0,0,.5);
    transform: scale(.9);
    transition: transform .18s ease, box-shadow .2s ease, background .2s ease, filter .2s ease;
    will-change: transform, filter;
  }
  .disc.black{ background: #0b0b0b; }
  .disc.white{ background: #fafafa; box-shadow: inset 0 8px 0 rgba(255,255,255,.8), 0 6px 12px rgba(0,0,0,.35)}
  .flip{animation: flip .2s ease}
  @keyframes flip{
    0%{ transform: rotateY(0) scale(.9) }
    50%{ transform: rotateY(90deg) scale(.9) }
    100%{ transform: rotateY(180deg) scale(.9) }
  }
  .status{
    margin:8px auto 0;
    text-align:center;
    color:var(--muted);
    min-height:1.4em;
  }
  .row-labels, .col-labels{display:grid;grid-template-columns:repeat(8,1fr);gap:6px;width:min(92vw, 720px);margin:0 auto;color:var(--muted);font-size:12px;letter-spacing:.08em}
  .row-labels{grid-template-columns:repeat(8,1fr)}
  .footer{margin-top:10px;text-align:center;color:var(--muted);font-size:12px}
  .toggle{
    appearance:none;width:44px;height:28px;border-radius:999px;display:inline-grid;place-items:center;position:relative;
    background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);cursor:pointer;vertical-align:middle
  }
  .toggle::after{content:"";width:20px;height:20px;border-radius:50%;background:white;position:absolute;left:4px;transition:all .15s ease}
  .toggle:checked::after{left:20px}
  .kbd{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono"; background:rgba(255,255,255,.06); border:1px solid rgba(255,255,255,.12); padding:2px 6px; border-radius:6px}
</style>
</head>
<body>
  <div class="wrap">
    <header>
      <div>
        <h1>リバーシ <span class="sub">(人 vs CPU / スマホ対応)</span></h1>
        <div class="sub">先手は<strong>黒(あなた)</strong>です。置ける場所は点線で表示されます。</div>
      </div>
      <div class="panel">
        <div class="badge score" id="scoreBox">
          <span class="dot black"></span><span id="blackScore">2</span>
          <span class="dot white" style="margin-left:8px"></span><span id="whiteScore">2</span>
        </div>
        <div class="badge" id="turnBox"><span class="dot black"></span><span id="turnText">あなたの手番</span></div>
      </div>
    </header>

    <div class="controls">
      <button id="newBtn">新規ゲーム</button>
      <button id="undoBtn" class="ghost" title="一手戻す(取り消し)">UNDO</button>
      <button id="hintBtn" class="ghost" title="候補に薄い石を表示">ヒント</button>
      <label class="badge" style="gap:8px">
        難易度
        <select id="level">
          <option value="1">かんたん</option>
          <option value="2" selected>ふつう</option>
          <option value="3">むずかしい</option>
        </select>
      </label>
      <label class="badge" style="gap:8px" title="CPUの思考アニメを省略">
        アニメ軽量
        <input id="liteAnim" type="checkbox" class="toggle">
      </label>
    </div>

    <div class="grid" id="board" aria-label="Reversi board" role="grid"></div>
    <div class="status" id="status"></div>
    <div class="footer">操作: マスをタップ / PCなら <span class="kbd">クリック</span>。ヒント: <span class="kbd">ヒント</span> ボタン。</div>
  </div>

<script>
(function(){
  const SIZE = 8;
  const EMPTY = 0, BLACK = 1, WHITE = -1;
  const DIRS = [[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];
  const CORNER_WEIGHTS = [
    [120,-20, 20,  5,  5, 20,-20,120],
    [-20,-40, -5, -5, -5, -5,-40,-20],
    [ 20, -5, 15,  3,  3, 15, -5, 20],
    [  5, -5,  3,  3,  3,  3, -5,  5],
    [  5, -5,  3,  3,  3,  3, -5,  5],
    [ 20, -5, 15,  3,  3, 15, -5, 20],
    [-20,-40, -5, -5, -5, -5,-40,-20],
    [120,-20, 20,  5,  5, 20,-20,120],
  ];
  const boardEl = document.getElementById('board');
  const statusEl = document.getElementById('status');
  const turnText = document.getElementById('turnText');
  const blackScoreEl = document.getElementById('blackScore');
  const whiteScoreEl = document.getElementById('whiteScore');
  const newBtn = document.getElementById('newBtn');
  const undoBtn = document.getElementById('undoBtn');
  const hintBtn = document.getElementById('hintBtn');
  const levelSel = document.getElementById('level');
  const liteAnim = document.getElementById('liteAnim');

  let state = {
    board: makeInitialBoard(),
    turn: BLACK, // human
    human: BLACK,
    cpu: WHITE,
    history: [],
    showHint:false,
    lock:false,
  };

  buildGrid();
  render(true);
  attachHandlers();

  function makeInitialBoard(){
    const b = Array.from({length:SIZE}, ()=> Array(SIZE).fill(EMPTY));
    const mid = SIZE/2;
    b[mid-1][mid-1] = WHITE;
    b[mid][mid] = WHITE;
    b[mid-1][mid] = BLACK;
    b[mid][mid-1] = BLACK;
    return b;
  }

  function cloneBoard(b){ return b.map(row=>row.slice()); }

  function inside(y,x){ return y>=0 && y<SIZE && x>=0 && x<SIZE; }

  function validMoves(b, player){
    const moves = [];
    for(let y=0;y<SIZE;y++){
      for(let x=0;x<SIZE;x++){
        if(b[y][x]!==EMPTY) continue;
        const flips = collectFlips(b,y,x,player);
        if(flips.length) moves.push({y,x,flips});
      }
    }
    return moves;
  }

  function collectFlips(b,y,x,player){
    if(b[y][x]!==EMPTY) return [];
    let flips = [];
    for(const [dy,dx] of DIRS){
      let ny=y+dy, nx=x+dx, tmp=[];
      while(inside(ny,nx) && b[ny][nx]===-player){
        tmp.push([ny,nx]); ny+=dy; nx+=dx;
      }
      if(tmp.length && inside(ny,nx) && b[ny][nx]===player) flips = flips.concat(tmp);
    }
    return flips;
  }

  function applyMove(b, move, player){
    const nb = cloneBoard(b);
    nb[move.y][move.x] = player;
    for(const [fy,fx] of move.flips) nb[fy][fx] = player;
    return nb;
  }

  function score(b){
    let black=0, white=0;
    for(let y=0;y<SIZE;y++) for(let x=0;x<SIZE;x++){
      if(b[y][x]===BLACK) black++;
      else if(b[y][x]===WHITE) white++;
    }
    return {black, white};
  }

  function gameOver(b){
    return validMoves(b,BLACK).length===0 && validMoves(b,WHITE).length===0;
  }

  function buildGrid(){
    boardEl.innerHTML = "";
    for(let i=0;i<SIZE*SIZE;i++){
      const cell = document.createElement('div');
      cell.className = 'cell';
      cell.setAttribute('role','gridcell');
      cell.dataset.i = i;
      cell.addEventListener('click', onCell);
      boardEl.appendChild(cell);
    }
  }

  function onCell(e){
    if(state.lock) return;
    const i = +e.currentTarget.dataset.i;
    const y = Math.floor(i/ SIZE), x = i % SIZE;
    const moves = validMoves(state.board, state.turn);
    const mv = moves.find(m=>m.y===y && m.x===x);
    if(!mv) return;
    pushHistory();
    state.board = applyMove(state.board, mv, state.turn);
    animatePlace(y,x,state.turn);
    state.turn = -state.turn;
    state.showHint = false;
    render();
    stepIfCpuTurn();
  }

  function pushHistory(){
    state.history.push({board: cloneBoard(state.board), turn: state.turn});
    if(state.history.length>120) state.history.shift();
  }

  function popHistory(){
    const last = state.history.pop();
    if(last){
      state.board = last.board;
      state.turn = last.turn;
    }
  }

  function render(initial=false){
    const cells = boardEl.children;
    const moves = validMoves(state.board, state.turn);
    const moveSet = new Set(moves.map(m=>m.y*SIZE+m.x));
    for(let y=0;y<SIZE;y++){
      for(let x=0;x<SIZE;x++){
        const idx = y*SIZE+x;
        const el = cells[idx];
        el.classList.toggle('valid', moveSet.has(idx) && !state.lock);
        el.classList.toggle('hint', state.showHint && moveSet.has(idx));
        // draw discs
        let disc = el.querySelector('.disc');
        const v = state.board[y][x];
        if(v!==EMPTY){
          if(!disc){
            disc = document.createElement('div');
            disc.className = 'disc ' + (v===BLACK?'black':'white') + (initial?'':' flip');
            el.appendChild(disc);
          }else{
            const want = v===BLACK?'black':'white';
            if(!disc.classList.contains(want)){
              disc.classList.toggle('black'); disc.classList.toggle('white');
              disc.classList.add('flip');
              setTimeout(()=>disc && disc.classList.remove('flip'), 210);
            }
          }
        }else{
          if(disc) disc.remove();
        }
      }
    }
    const sc = score(state.board);
    blackScoreEl.textContent = sc.black;
    whiteScoreEl.textContent = sc.white;
    const yourTurn = state.turn===state.human;
    turnText.innerHTML = yourTurn ? "あなたの手番" : "CPU思考中…";
    if(gameOver(state.board)){
      const msg = sc.black===sc.white ? "引き分け!" : (sc.black>sc.white ? "あなたの勝ち!🎉" : "CPUの勝ち…");
      statusEl.textContent = `ゲーム終了:${msg}`;
      turnText.textContent = "終了";
    }else{
      const canMove = validMoves(state.board, state.turn).length>0;
      statusEl.textContent = canMove? "" : "パスです。";
    }
    undoBtn.disabled = state.history.length===0;
  }

  function animatePlace(y,x,player){
    if(liteAnim.checked) return;
    const idx = y*SIZE+x;
    const el = boardEl.children[idx];
    const disc = el.querySelector('.disc');
    if(disc){
      disc.style.transform = 'scale(1.06)';
      disc.style.filter = 'drop-shadow(0 0 8px rgba(102,224,163,.6))';
      setTimeout(()=>{
        if(!disc) return;
        disc.style.transform = 'scale(.9)';
        disc.style.filter = '';
      }, 120);
    }
  }

  function stepIfCpuTurn(){
    if(gameOver(state.board)) { render(); return; }
    const movesMe = validMoves(state.board, state.turn);
    if(movesMe.length===0){
      // pass
      state.turn = -state.turn;
      render();
      if(state.turn===state.cpu) setTimeout(stepIfCpuTurn, 50);
      return;
    }
    if(state.turn===state.cpu){
      state.lock = true;
      render();
      const depth = levelSel.value==='1' ? 1 : (levelSel.value==='2' ? 2 : 3);
      setTimeout(()=>{
        const mv = aiBestMove(state.board, state.cpu, depth);
        state.lock = false;
        if(!mv){ // no move (shouldn't happen here)
          state.turn = -state.turn; render(); return;
        }
        pushHistory();
        state.board = applyMove(state.board, mv, state.cpu);
        animatePlace(mv.y, mv.x, state.cpu);
        state.turn = -state.cpu;
        render();
        // If human must pass, chain CPU
        const nextMoves = validMoves(state.board, state.turn);
        if(nextMoves.length===0 && !gameOver(state.board)){
          state.turn = -state.turn;
          render();
          setTimeout(stepIfCpuTurn, 70);
        }
      }, liteAnim.checked ? 30 : 260);
    }
  }

  // ---------- AI ----------
  function aiBestMove(b, player, depth){
    const moves = validMoves(b, player);
    if(moves.length===0) return null;
    // Opening heuristic: prefer corners
    const corner = moves.find(m=>isCorner(m.y,m.x));
    if(depth<=1 && corner) return corner;
    let best = -Infinity, bestMove = moves[0];
    let alpha = -Infinity, beta = Infinity;
    for(const m of moves){
      const nb = applyMove(b, m, player);
      const val = -negamax(nb, -player, depth-1, -beta, -alpha);
      if(val > best){ best = val; bestMove = m; }
      if(val > alpha) alpha = val;
      if(alpha >= beta) break; // alpha-beta pruning
    }
    return bestMove;
  }

  function negamax(b, player, depth, alpha, beta){
    if(depth===0 || gameOver(b)) return evaluate(b, player);
    const moves = validMoves(b, player);
    if(moves.length===0){
      if(validMoves(b, -player).length===0) return evaluate(b, player);
      return -negamax(b, -player, depth-1, -beta, -alpha); // pass
    }
    let best = -Infinity;
    for(const m of moves){
      const nb = applyMove(b, m, player);
      const val = -negamax(nb, -player, depth-1, -beta, -alpha);
      if(val>best) best = val;
      if(val>alpha) alpha = val;
      if(alpha>=beta) break;
    }
    return best;
  }

  function evaluate(b, forPlayer){
    // Heuristic: (corner-weighted material) + mobility + corner control + stability-ish
    let mat = 0, myMoves=0, oppMoves=0, cornerCtrl=0, edges=0;
    const s = score(b);
    mat = (s.black - s.white) * (forPlayer===BLACK?1:-1);

    for(let y=0;y<SIZE;y++){
      for(let x=0;x<SIZE;x++){
        const v = b[y][x];
        if(v!==EMPTY){
          const w = CORNER_WEIGHTS[y][x];
          mat += (v===forPlayer? w : -w)*0.35;
          if(isEdge(y,x)) edges += (v===forPlayer?1:-1)*0.4;
          if(isCorner(y,x)) cornerCtrl += (v===forPlayer?1:-1)*20;
        }
      }
    }
    myMoves = validMoves(b, forPlayer).length;
    oppMoves = validMoves(b, -forPlayer).length;
    const mobility = (myMoves - oppMoves) * 2.2;

    // Phase weighting: early emphasize position, late emphasize disks
    const filled = s.black + s.white;
    const phase = filled/64; // 0..1
    const val = (1-phase)* (mobility + edges + cornerCtrl) + phase * (mat*0.8) + mat*0.15;
    return val;
  }

  function isCorner(y,x){ return (y===0||y===SIZE-1) && (x===0||x===SIZE-1); }
  function isEdge(y,x){ return y===0 || y===SIZE-1 || x===0 || x===SIZE-1; }

  // ---------- UI Handlers ----------
  function attachHandlers(){
    newBtn.addEventListener('click', ()=>{
      state.board = makeInitialBoard();
      state.turn = BLACK;
      state.history = [];
      state.showHint = false;
      render(true);
    });
    undoBtn.addEventListener('click', ()=>{
      if(state.lock) return;
      // Undo pair (CPU + Human) if CPU just moved; else undo one
      popHistory(); // undo last
      if(state.turn===state.cpu) popHistory(); // keep it user's turn
      render();
    });
    hintBtn.addEventListener('click', ()=>{
      state.showHint = !state.showHint;
      render();
    });
    levelSel.addEventListener('change', ()=>{
      // nothing special; affects next CPU move
    });
    // Keyboard support (optional)
    window.addEventListener('keydown', (e)=>{
      if(e.key==='h') { state.showHint=!state.showHint; render(); }
      if((e.ctrlKey||e.metaKey) && e.key.toLowerCase()==='z'){ undoBtn.click(); }
      if(e.key==='n') newBtn.click();
    }, {passive:true});
  }
})();
</script>
</body>
</html>

コピーしたら「ミニコデ」を開いてください。※別タブで開くのでご注意を

\ いますぐタップ! /

\ 今すぐタップ /

開いたら赤枠部分をタップして①貼り付け、②プレビュータブをタップをしてください。

リバーシの画面が表示されれば完了です。

ちなみにAIは同じ指示をしても、必ず同じ結果になるわけではありません。

おそらく、皆さんのAIが作成したソースコードを貼り付けると、上記の見た目と違うものが出てきたはずです。

思っているのとなんか違う…

こんな風に思った方もいるのではないでしょうか?

本サイトでは「皆さんが作りたいもの」を生成AIに作らせるためのテクニックもご紹介していく予定です。

今回は超基本的なAIの利用方法とミニコデの超基本の操作方法を理解していただければOKです。

欲張ってもよいことないですからね。

まとめ

プログラミングって、環境準備、学習、プログラムを書く、動かす、バグ、直す・・・ってやっているうちに嫌になって挫折してしまうんですよね。

その点、ミニコデと生成AIを組み合わせれば、スマホ一台でも「とりあえず作る」がすぐに実現できます。

作ったものがすぐに動くと、次の開発の原動力にもなりますし、わからないこともAIに聞けばいいのでプログラミングが楽しくなっていくんですよ。

でも、これだと開発してるのAIだよね?

はい、おっしゃる通りです。ただ、あなたの頭の中にあったプログラムのイメージをAIが実現してくれているので、共同開発と解釈してもらえると嬉しいです。

AIでプログラミングをしていると、「自分でプログラムを組みたい」って人も出てくると思っています。

本サイトをきかっかけに未来の優秀なプログラマーやSEの育成のきっかけにつながるといいなと思っています。

まずはプログラムを身近なものととらえて慣れ親しんでくれれば幸いです。

最後にミニコデの使い方を以下のページで掲載してますので、一度ご覧ください。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次