本文へスキップ

Clamini library

15パズル

 Pascalでプログラミングの練習がてら作ってみた、15パズル風絵合わせです。内容はシンプルで、バラバラになった絵を完成させるスタイル。 集中力が試されるので、ちょっとした頭の体操にもなります!

ゲーム画面

15パズル

概要

タイトル15パズル
ジャンル絵合わせ
プレイ人数1人
動作環境Windows11 (64bit)

操作方法

左クリックスライドする

使い方

ダウンロード


 下のリンクからファイルをダウンロードできます。

 ※ZIP形式で圧縮されてるので、使う前に解凍してね! puzzle15.zip

 本ソフトは Free Pascal 及び Lazarus を使用して開発されました。
 本ソフトは SDL2-for-Pascal を使用しています。SDL2-for-Pascal は Zlib ライセンスの下で利用しています。
 本ソフトはライブラリ(SDL2 (Simple DirectMedia Layer))を使用しています。

遊び方


 ゲームを起動すると、バラバラになった絵が表示されます。 元の絵は、400×400サイズのBMPファイルが使用でき、photo.bmpと差し替えれば何でも使うことができます! 左クリックで絵をスライドさせることができるので、頑張って完成させましょう。

ソースコード


{$APPTYPE GUI}
program puzzle15;

uses
  SDL2, SysUtils, Math;

const
  SCREEN_WIDTH = 400;   // ウィンドウの幅
  SCREEN_HEIGHT = 400;  // ウィンドウの高さ
  TILE_SIZE = 100;      // 1タイルのサイズ(100x100ピクセル)
  SHUFFLE_STEPS = 200;  // シャッフルする回数

var
  window: PSDL_Window;
  renderer: PSDL_Renderer;
  sdlEvent: TSDL_Event;
  running: Boolean = True;     // メインループ実行フラグ
  isCleared: Boolean = False;  // クリア判定フラグ
  photoTexture: PSDL_Texture;  // 分割前の元画像テクスチャ
  imageSurface: PSDL_Surface;
  board: array[0..3, 0..3] of Integer; // パズルの盤面データ(0が空きマス、1-15がタイルの番号)
  emptyX, emptyY: Integer;             // 現在の空きマスの位置
  i, j, tx, ty, val, srcX, srcY: Integer;
  srcR, dstR: TSDL_Rect;

{ --- 現在の盤面が正解(1〜15が順に並んでいるか)をチェック --- }
function CheckClear: Boolean;
var
  i, j: Integer;
begin
  for j := 0 to 3 do
    for i := 0 to 3 do
    begin
      // 右下の最後のマスはチェック対象外(空きマスになるため)
      if (i = 3) and (j = 3) then Continue;

      // 計算上の正解数値と一致しているか確認
      if board[i, j] <> (j * 4 + i + 1) then Exit(False);
    end;
  Result := True;
end;

{ --- 盤面をランダムに動かしてパズルを生成 --- }
procedure Shuffle;
var
  steps, dir, nx, ny: Integer;
begin
  Randomize;
  steps := 0;
  while steps < SHUFFLE_STEPS do
  begin
    nx := emptyX;
    ny := emptyY;
    dir := Random(4); // 0〜3の方向をランダム決定
    case dir of
      0: Dec(ny); // 上に移動
      1: Inc(ny); // 下に移動
      2: Dec(nx); // 左に移動
      3: Inc(nx); // 右に移動
    end;

    // 移動先が盤面内に収まっている場合、空きマスと中身を入れ替える
    if (nx >= 0) and (nx < 4) and (ny >= 0) and (ny < 4) then
    begin
      board[emptyX, emptyY] := board[nx, ny];
      board[nx, ny] := 0;
      emptyX := nx;
      emptyY := ny;
      Inc(steps);
    end;
  end;
end;

{ --- メインプログラム開始 --- }
begin
  // SDL2の初期化
  if SDL_Init(SDL_INIT_VIDEO) < 0 then Exit;

  // ウィンドウとレンダラーの作成
  window := SDL_CreateWindow('SDL2 15 Puzzle',
    SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
    SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
  renderer := SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

  // 画像の読み込み(ビットマップ形式)
  imageSurface := SDL_LoadBMP('photo.bmp');
  if imageSurface = nil then
  begin
    WriteLn('Error: photo.bmp not found!');
    running := False; // 画像がない場合は実行を中止
  end;

  if running then
  begin
    // サーフェスをテクスチャに変換し、不要になったサーフェスを解放
    photoTexture := SDL_CreateTextureFromSurface(renderer, imageSurface);
    SDL_FreeSurface(imageSurface);

    // 盤面の初期化(正解の形に並べる)
    for j := 0 to 3 do
      for i := 0 to 3 do
        board[i, j] := (j * 4) + i + 1;

    board[3, 3] := 0; // 右下を空きマスに設定
    emptyX := 3;
    emptyY := 3;

    Shuffle; // パズルをバラバラにする
  end;

  { --- メインイベントループ --- }
  while running do
  begin
    while SDL_PollEvent(@sdlEvent) <> 0 do
    begin
      case sdlEvent.type_ of
        // ウィンドウの×ボタンが押されたとき
        SDL_QUITEV: running := False;

        // マウスクリック時の処理
        SDL_MOUSEBUTTONDOWN:
          if not isCleared then // クリア後は操作不可
          begin
            // クリックされたマスのインデックスを算出
            tx := sdlEvent.button.x div TILE_SIZE;
            ty := sdlEvent.button.y div TILE_SIZE;

            // 空きマスに隣接しているか判定(マンハッタン距離が1なら隣)
            if (Abs(tx - emptyX) + Abs(ty - emptyY) = 1) then
            begin
              // 入れ替え処理
              board[emptyX, emptyY] := board[tx, ty];
              board[tx, ty] := 0;
              emptyX := tx;
              emptyY := ty;

              // 移動ごとにクリアチェック
              if CheckClear then
              begin
                isCleared := True;
                SDL_SetWindowTitle(window, '15 Puzzle - GAME CLEAR!');
              end;
            end;
          end;
      end;
    end;

    { --- 描画処理 --- }
    SDL_SetRenderDrawColor(renderer, 30, 30, 30, 255); // 背景色(暗いグレー)
    SDL_RenderClear(renderer);

    for j := 0 to 3 do
      for i := 0 to 3 do
      begin
        val := board[i, j];

        // クリア時は空きマス(0)の部分に本来の16枚目のピース(右下隅)を描画する
        if (val = 0) and isCleared then val := 16;

        if val <> 0 then
        begin
          // 画像のどの部分を切り出すか計算 (Source Rect)
          srcX := (val - 1) mod 4;
          srcY := (val - 1) div 4;
          srcR.x := srcX * TILE_SIZE;
          srcR.y := srcY * TILE_SIZE;
          srcR.w := TILE_SIZE;
          srcR.h := TILE_SIZE;

          // 画面上のどこに描画するか計算 (Destination Rect)
          dstR.x := i * TILE_SIZE;
          dstR.y := j * TILE_SIZE;
          dstR.w := TILE_SIZE;
          dstR.h := TILE_SIZE;

          // 元画像から指定範囲を切り取って盤面にコピー
          SDL_RenderCopy(renderer, photoTexture, @srcR, @dstR);
        end;
      end;

    SDL_RenderPresent(renderer); // 画面を更新
    SDL_Delay(16);               // 約60FPSを維持するための待機
  end;

  { --- 終了処理 --- }
  if photoTexture <> nil then SDL_DestroyTexture(photoTexture);
  SDL_DestroyRenderer(renderer);
  SDL_DestroyWindow(window);
  SDL_Quit;
end.