M.Hiroi's Home Page
http://www.geocities.jp/m_hiroi/

Puzzle DE Programming

変形版「ライツアウト」

[ Home | Puzzle ]

問題の説明

ライツアウトは「(株)タカラ」から1995年に発売されたパズルゲーム機で、光っているボタンをすべて消すことができればクリアとなります。ライツアウトのルールは拙作の ライツアウトの解法 をお読みください。

ところで、ライツアウトの変形バージョンではボタンの色を増やした N 色ライツアウトが有名ですが、このほかにもボタンを押したときの反転パターンや盤面の形を変えたバージョンを考えることができます。ここでは 3 つの変形版「ライツアウト」を取り上げてみましょう。


●反転パターンをXに変更

最初にボタンを押したときの反転パターンを変更してみましょう。反転パターンは M.Hiroi が X68000 ユーザーだからというわけではありませんが、次のようなXの形にしてみました。

  □□□□□      □□□□□  
  □□□□□      □■□■□
  □□□□□ ─→ □□■□□
  □□□□□      □■□■□
  □□□□□      □□□□□

   中央のボタンを押した場合

    図:反転パターンの変更

基本的な法則はライツアウトと同じなので、ボタンを押す組み合わせは全部で 2^25 (2 の 25 乗)、約 32 万通りになります。このパターンだと上から 1 行ずつ消していく方法は使えませんが、細江万太郎さんが考案された「ライツアウトを連立方程式で解く方法」で高速に解くことができるはずです。興味のある方は、高橋謙一郎さんの コンピュータ&パズル にある 驚異のライツアウト解法ロジック をお読みください。そこで、今回は解法プログラムではなく最長手数を調べることにしました。

プログラムは ライツアウトの解法:最長手数を求める の反転パターンを変更するだけです。結果は次のようになりました。

 1手のパターン数     25
 2手のパターン数    300
 3手のパターン数   2300
 4手のパターン数  12442
 5手のパターン数  49402
 6手のパターン数 145824
 7手のパターン数 317136
 8手のパターン数 493505
 9手のパターン数 525929
10手のパターン数 363348
11手のパターン数 150868
12手のパターン数  33156
13手のパターン数   2916
合計 2097152 個

実行時間は M.Hiroi のオンボロマシン (Pentium 166 MHz) で約 42 秒、ライトがすべて消えている完成形も含めて全部で 2,097,152 通りとなりました。ライツアウトの 1/4 しかありません。最長手数も 13 手と短くなりました。パターンの総数が少ないので、ライツアウトよりも簡単なように思われます。ですが、実際のゲームでは 1 行ずつ消していく方法が使えないので、ライツアウトよりも難しく感じるかもしれませんね。

プログラムを読む


●点灯と消灯で反転パターンを変える

次はボタンの点灯と消灯で反転パターンが異なるライツアウトを考えてみましょう。次の図を見てください。

  □□□      □■□    ■■■      □■□  
  □□□ ─→ ■■■    ■■■ ─→ ■□■  
  □□□      □■□    ■■■      □■□  

   消灯時のパターン      点灯時のパターン
    (どちらも中央のボタンを押した場合)

            図:変形版ライツアウト

上図に示すように、消灯しているボタンを押したときは今までと同じですが、点灯しているボタンを押すとXの形(押したボタンと斜め 4 方向のボタン)に反転します。このパターンの面白いところは、同じボタンを 4 回押すと元の状態に戻ることです。次の図を見てください。

  □□□      □■□      ■■■      ■□■      □□□ 
  □□□ ─→ ■■■ ─→ ■□■ ─→ □■□ ─→ □□□ 
  □□□      □■□      ■■■      ■□■      □□□ 

        図:同じボタンを 4 回押すと元の状態に戻る

一見すると 4 色ライツアウトと同じようですが、ライトの状態はオン・オフの 2 種類しかないので、局面の総数はそれほど多くはないでしょう。5 行 5 列盤の場合、局面は最大で 2 ^ 25 = 33554432 通りですが、ライツアウトではその 1/4 の 8388608 通りしかありません。これより多くなるのか少なくなるのか、ちょっと興味があります。そこで、局面の総数と最長手数を求めるプログラムを作ってみました。

実行結果を示します。実行時間は M.Hiroi のオンボロマシン (Pentium 166 MHz) で約 200 秒かかりました。

 1手のパターン数 25
 2手のパターン数 357
 3手のパターン数 4021
 4手のパターン数 37809
 5手のパターン数 299268
 6手のパターン数 1935410
 7手のパターン数 8667086
 8手のパターン数 16621708
 9手のパターン数 5852793
10手のパターン数 135954
合計 33554432 個

局面の総数は初期状態を含めて 33554432 通りになりました。つまり、ライトがどんな点灯パターンでも必ず解くことができるわけです。この結果は予想していなかったので、M.Hiroi もびっくりしました。ライツアウトの最長手数は 15 手ですが、この変形版は 10 手と短くなりました。もしかすると、ライツアウトよりも簡単に解けるのかもしれませんね。

プログラムを読む


●菱形ライツアウト

最後は、下図のように盤面の形を菱形に変えてみましょう。

        □
      □□□
    □□□□□
  □□□□□□□  
    □□□□□
      □□□
        □

図:菱形ライツアウト

ボタンは全部で 25 個あります。反転パターンはライツアウトと同じく、押したボタンと左右上下のボタンの状態が反転します。局面の総数と最長手数はライツアウトよりも増えるのか減るのか、プログラムを作って調べてみました。結果は次のようになりました。

 1手のパターン数 25
 2手のパターン数 300
 3手のパターン数 2300
 4手のパターン数 12650
 5手のパターン数 53130
 6手のパターン数 177100
 7手のパターン数 480700
 8手のパターン数 1081575
 9手のパターン数 2042975
10手のパターン数 3268760
11手のパターン数 4457400
12手のパターン数 5200300
13手のパターン数 5200300
14手のパターン数 4457400
15手のパターン数 3268760
16手のパターン数 2042975
17手のパターン数 1081575
18手のパターン数 480700
19手のパターン数 177100
20手のパターン数 53130
21手のパターン数 12650
22手のパターン数 2300
23手のパターン数 300
24手のパターン数 25
25手のパターン数 1
合計 33554432 個 (初期状態を含む)

実行時間は M.Hiroi のオンボロマシン (Pentium 166 MHz) で約 4 分かかりました。生成した局面数は初期状態を含めて 33554432 通りあります。これは、どんな点灯パターンでも必ず解くことができることを表しています。それから、最長手数は 25 手、つまりすべてのボタンを押した場合になります。この局面は下図のようなパターンになります。

        □
      ■■■
    ■■■■■
  □■■■■■□  
    ■■■■■
      ■■■
        □

図:最長手数の局面

プログラムを読む


●プログラムリスト1

/*
 * lo_x.c : パターンを X に変えたライツアウト
 *          最長手数を求める
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_SIZE 0x2000000
#define SIZE     25

/* チェックテーブル */
char *check_table;

/* 反転パターン */
int pattern[SIZE] = {
  0x0000041, 0x00000a2, 0x0000144, 0x0000288, 0x0000110,
  0x0000822, 0x0001445, 0x000288a, 0x0005114, 0x0002208,
  0x0010440, 0x00288a0, 0x0051140, 0x00a2280, 0x0044100,
  0x0208800, 0x0511400, 0x0a22800, 0x1445000, 0x0882000,
  0x0110000, 0x0228000, 0x0450000, 0x08a0000, 0x1040000,
};

/* ボタンを押す */
int push_button( int now_pat, int move )
{
  int i;
  int c = 0;
  for( i = 0; i < SIZE; i++ ){
    int new_pat = now_pat ^ pattern[i];
    if( !check_table[new_pat] ){
      check_table[new_pat] = move;
      c++;
    }
  }
  return c;
}

/* 力任せの 32 万回ループ */
void solve( void )
{
  int i, move, c, total = 1;
  /* 最初は 0 からスタート */
  move = 1;
  c = push_button( 0, move );
  total += c;
  printf("%2d手のパターン数 %d\n", move, c );
  check_table[0] = -1;

  /* 2 手目以降の探索 */
  for( ; move < SIZE; move++ ){
    c = 0;
    for( i = 0; i < MAX_SIZE; i++ ){
      if( check_table[i] == move ){
        c += push_button( i, move + 1 );
      }
    }
    /* 新しいパターンが生成されなければ探索終了 */
    if( c == 0 ) break;
    printf("%2d手のパターン数 %d\n", move + 1, c );
    total += c;
  }
  printf("合計 %d 個\n", total );
}

int main()
{
  int s, e;
  /* メモリの確保 */
  check_table = calloc( MAX_SIZE, sizeof( char ) );
  if( check_table == NULL ){
    fprintf( stderr, "Out of memory!!\n");
    exit( 1 );
  }
  s = clock();
  solve();
  e = clock();
  printf("時間 %d\n", e - s );
  return 0;
}

戻る


●プログラムリスト2

/*
 * lo_plus_x.c : 変形版ライツアウト(+とXのパターン)
 *               最長手数を求める
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_SIZE 0x2000000
#define SIZE     25

/* チェックテーブル */
char *check_table;

/* on off チェックテーブル */
int bit_on[SIZE];

/* 反転パターン(+ pattern) */
int pattern[SIZE] = {
  0x0000023,  0x0000047,  0x000008e,  0x000011c,  0x0000218,
  0x0000461,  0x00008e2,  0x00011c4,  0x0002388,  0x0004310,
  0x0008c20,  0x0011c40,  0x0023880,  0x0047100,  0x0086200,
  0x0118400,  0x0238800,  0x0471000,  0x08e2000,  0x10c4000,
  0x0308000,  0x0710000,  0x0e20000,  0x1c40000,  0x1880000,
};

/* 反転パターン(X pattern) */
int pattern_x[SIZE] = {
  0x0000041,  0x00000a2,  0x0000144,  0x0000288,  0x0000110,
  0x0000822,  0x0001445,  0x000288a,  0x0005114,  0x0002208,
  0x0010440,  0x00288a0,  0x0051140,  0x00a2280,  0x0044100,
  0x0208800,  0x0511400,  0x0a22800,  0x1445000,  0x0882000,
  0x0110000,  0x0228000,  0x0450000,  0x08a0000,  0x1040000,
};

/*
 * ボタンを押す
 * 逆順だから消灯時が X で点灯時が + になる
 */
int push_button( int now_pat, int move )
{
  int i;
  int c = 0;
  for( i = 0; i < SIZE; i++ ){
    int new_pat;
    if( bit_on[i] & now_pat ){
      new_pat = now_pat ^ pattern[i];
    } else {
      new_pat = now_pat ^ pattern_x[i];
    }
    if( !check_table[new_pat] ){
      check_table[new_pat] = move;
      c++;
    }
  }
  return c;
}

/* 力任せの 32 万回ループ */
void solve( void )
{
  int i, move, c, total = 1;
  /* 最初は 0 からスタート */
  move = 1;
  c = push_button( 0, move );
  total += c;
  printf("%2d手のパターン数 %d\n", move, c );
  check_table[0] = -1;

  /* 2 手目以降の探索 */
  for( ; move < SIZE; move++ ){
    c = 0;
    for( i = 0; i < MAX_SIZE; i++ ){
      if( check_table[i] == move ){
        c += push_button( i, move + 1 );
      }
    }
    /* 新しいパターンが生成されなければ探索終了 */
    if( c == 0 ) break;
    printf("%2d手のパターン数 %d\n", move + 1, c );
    total += c;
  }
  printf("合計 %d 個\n", total );
}

int main()
{
  int i, s, e;
  /* メモリの確保 */
  check_table = calloc( MAX_SIZE, sizeof( char ) );
  if( check_table == NULL ){
    fprintf( stderr, "Out of memory!!\n");
    exit( 1 );
  }
  /* 初期化 */
  for( i = 0; i < SIZE; i++ ) bit_on[i] = 1 << i;
  /* 実行 */
  s = clock();
  solve();
  e = clock();
  printf("時間 %d\n", e - s );
  return 0;
}

戻る


●プログラムリスト3

/*
 * lo_trr.c : 菱形ライトアウトの最長手数を求める
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define MAX_SIZE 0x2000000
#define SIZE     25

/* チェックテーブル */
char *check_table;

/* 反転パターン */
int pattern[SIZE];

/*        座標
           0
        1 2 3
     4 5 6 7 8
  9 10 11 12 13 14 15
     16 17 18 19 20
        21 22 23
           24
*/
const char pattern_data[][SIZE] = {
  { 0, 2, -1},               /* 0 */
  { 1, 2, 5, -1},            /* 1 */
  { 0, 1, 2, 3, 6, -1},      /* 2 */
  { 2, 3, 7, -1},            /* 3 */
  { 4, 5, 10, -1},           /* 4 */
  { 1, 4, 5, 6, 11, -1},     /* 5 */
  { 2, 5, 6, 7, 12, -1},     /* 6 */
  { 3, 6, 7, 8, 13, -1},     /* 7 */
  { 7, 8, 14, -1},           /* 8 */
  { 9, 10, -1},              /* 9 */
  { 4, 9, 10, 11, 16, -1},   /* 10 */
  { 5, 10, 11, 12, 17, -1},  /* 11 */
  { 6, 11, 12, 13, 18, -1},  /* 12 */
  { 7, 12, 13, 14, 19, -1},  /* 13 */
  { 8, 13, 14, 15, 20, -1},  /* 14 */
  { 14, 15, -1},             /* 15 */
  { 10, 16, 17, -1},         /* 16 */
  { 11, 16, 17, 18, 21, -1}, /* 17 */
  { 12, 17, 18, 19, 22, -1}, /* 18 */
  { 13, 18, 19, 20, 23, -1}, /* 19 */
  { 14, 19, 20, -1},         /* 20 */
  { 17, 21, 22, -1},         /* 21 */
  { 18, 21, 22, 23, 24, -1}, /* 22 */
  { 19, 22, 23, -1},         /* 23 */
  { 22, 24, -1},             /* 24 */
};

/* 反転パターンを作る */
void make_pattern( void )
{
  int i;
  for( i = 0; i < SIZE; i++ ){
    int j, k,  pat = 0;
    for( j = 0; (k = pattern_data[i][j]) != -1; j++ ){
      pat += 1 << k;
    }
    pattern[i] = pat;
  }
}

/* ボタンを押す */
int push_button( int now_pat, int move )
{
  int i;
  int c = 0;
  for( i = 0; i < SIZE; i++ ){
    int new_pat = now_pat ^ pattern[i];
    if( !check_table[new_pat] ){
      check_table[new_pat] = move;
      c++;
    }
  }
  return c;
}

/* 力任せの 32 万回 ループ */
void solve( void )
{
  int i, move, c, total = 1;
  /* 最初は 0 からスタート */
  move = 1;
  c = push_button( 0, move );
  total += c;
  printf("%2d手のパターン数 %d\n", move, c );
  check_table[0] = -1;

  /* 2 手目以降の探索 */
  for( ; move < SIZE; move++ ){
    c = 0;
    for( i = 0; i < MAX_SIZE; i++ ){
      if( check_table[i] == move ){
        c += push_button( i, move + 1 );
      }
    }
    /* 新しいパターンが生成されなければ探索終了 */
    if( c == 0 ) break;
    printf("%2d手のパターン数 %d\n", move + 1, c );
    total += c;
  }
  printf("合計 %d 個\n", total );
}

int main()
{
  int s, e;
  /* メモリの確保 */
  check_table = calloc( MAX_SIZE, sizeof( char ) );
  if( check_table == NULL ){
    fprintf( stderr, "Out of memory!!\n");
    exit( 1 );
  }
  make_pattern();
  s = clock();
  solve();
  e = clock();
  printf("時間 %d\n", e - s );
  return 0;
}

戻る


Copyright (C) 2002,2003 Makoto Hiroi
All rights reserved.

[ Home | Puzzle ]