ゲームセンターに設置してあるメダルゲームの中に魚釣りをテーマにした台がある。今回はそのゲームのメインの演出部分をJavaで再現してみよう。
最後にジャックポットを獲得する確率がどのくらいなのかもシミュレーションしてみる。
お題
100m先にいる、大物のお魚を釣り上げることができれば成功(ジャックポット)だ。
これは毎回
20m,10m,5m,1mが抽選され、その分だけ巻き取ることができる。
もし、運良く20mを引ければ、残りの距離は80mになる。
この抽選の際の確率は
20m…1/6(6分の1)
10m 2/6
5m 2/6
1m 1/6
となっている。
これを繰り返しおこなうわけだが、糸の耐久力というのが設定されていて糸が切れると失敗となってしまう。
糸のダメージは最初2に設定されていてこれが5になってしまったらアウトだ。
この糸に与えるダメージも毎回ランダムに抽選される。
Great (-1)
Good (0)
Bad (1)
の3種類が存在し、運良くGreatをひけた場合は糸のダメージが1減少する。
Goodは糸にダメージを与えない。
Badは1のダメージを与えることとなる。
この3種類の発生確率は以下
Great 1/10
Good 3/10
Bad 6/10
このゲームのポイントは以下にBadを引かずに継続できるかがポイントなる。実際のゲームでもそうだがGreatを引けると激アツとなる。
実行例
失敗
釣りゲーム
Press enter Key...
1m!
Good
残り:99m
ダメージ:2/5
20m!
Bad
残り:79m
ダメージ:3/5
5m!
Bad
残り:74m
ダメージ:4/5
10m!
Great!!!
残り:64m
ダメージ:3/5
10m!
Good
残り:54m
ダメージ:3/5
5m!
Good
残り:49m
ダメージ:3/5
10m!
Good
残り:39m
ダメージ:3/5
5m!
Bad
残り:34m
ダメージ:4/5
10m!
Bad
残り:24m
ダメージ:5/5
糸が切れてしまった。。。失敗
成功
釣りゲーム
Press enter Key...
20m!
Great!!!
残り:80m
ダメージ:1/5
10m!
Great!!!
残り:70m
ダメージ:0/5
5m!
Bad
残り:65m
ダメージ:1/5
10m!
Great!!!
残り:55m
ダメージ:0/5
20m!
Good
残り:35m
ダメージ:0/5
1m!
Good
残り:34m
ダメージ:0/5
20m!
Bad
残り:14m
ダメージ:1/5
10m!
Bad
残り:4m
ダメージ:2/5
1m!
Bad
残り:3m
ダメージ:3/5
10m!
Great!!!
残り:0m
ダメージ:2/5
釣り上げることに成功した!
仕様
○糸を100m巻き取ると成功
○糸のダメージが5になったら失敗(初期値は2)
○残りの距離が0とダメージ5が同じ回に発生した場合は成功
○ダメージは0より小さくはならない(0のときにGreatを引いても0のまま)
○残りの距離は0より小さくはならない(残り5m時に20mを引いた場合0となる)
○各ターンはエンターキーを入力することで進む
○各ターンでの巻取量発生確率
20m 1/6
10m 2/6
5m 2/6
1m 1/6
○各ターンでの糸のダメージ発生確率とダメージ量
Great 1/10 ダメージ1回復
Good 3/10 ダメージ発生せず
Bad 6/10 ダメージ量1
Let’s challenge!
難易度的にはさほど難しくはない、paizaでいうとCランクくらいの難度となる。
挑戦してみよう。
作例
では、一緒に作成していこう。後でリファクタリングを行う前提でまずは最短距離での処理の実現を目指す。扱う事象がさほど複雑ではないので今回は一般クラスは作成せずにアプリケーションクラスのみで行う。
まずは以下のようにFishingAppクラスを作成する。
import java.util.*;
public class FishingApp{
public static void main(String[] args){
//巻取量テーブル
int[] LEN_ARR= {20,10,10,5,5,1};
//ダメージテーブル
int[] DAMAGE_ARR= { -1,0,0,0,1,1,1,1,1,1,};
//ダメージ表記
String[] DAMAGE_MESSAGES= {"Great!!!","Good","Bad"};
//残りの距離
int length=100;
//現在のダメージ
int damage=2;
}
}
ポイント
確率を扱う手法は色々あるが、今回のように母数が少ない場合は上記のように配列を用いて列挙していまうのが何かとラクだ。続いて以下を追記
import java.util.*;
public class FishingApp{
public static void main(String[] args){
//巻取量テーブル
int[] LEN_ARR= {20,10,10,5,5,1};
//ダメージテーブル
int[] DAMAGE_ARR= { -1,0,0,0,1,1,1,1,1,1,};
//ダメージ表記
String[] DAMAGE_MESSAGES= {"Great!!!","Good","Bad"};
//残りの距離
int length=100;
//現在のダメージ
int damage=2;
//ランダムインスタンス生成
Random rand=new Random();
//スキャナーインスタンス生成
Scanner sc = new Scanner(System.in);
//初期表示
System.out.println("釣りゲーム");
System.out.println("Press enter Key...");
}
}
ポイント
確率を扱うのでRandomインスタンス。エンターキーを叩くことによって処理を進めたいのでその際に必要なScannerインスタンスを生成している。
続いて以下を追記
import java.util.Random;
import java.util.Scanner;
public class FishingApp2 {
public static void main(String[] args) {
//巻取量テーブル
int[] LEN_ARR= {20,10,10,5,5,1,};
//ダメージテーブル
int[] DAMAGE_ARR= { -1,0,0,0,1,1,1,1,1,1,};
//ダメージ表記
String[] DAMAGE_MESSAGES= {"Great!!!","Good","Bad"};
//残りの距離
int length=100;
//現在のダメージ
int damage=2;
//ランダムインスタンス生成
Random rand=new Random();
//スキャナーインスタンス生成
Scanner sc = new Scanner(System.in);
//初期表示
System.out.println("釣りゲーム");
System.out.println("Press enter Key...");
//残りの長さが0より大きくかつダメージが5より小さい間
//継続するループ
while(length > 0 && damage < 5) {
//エンターキーで進む処理
sc.nextLine();
//巻取り長さの抽選
int len = LEN_ARR[rand.nextInt(LEN_ARR.length)];
//ダメージの抽選
int dmg = DAMAGE_ARR[rand.nextInt(DAMAGE_ARR.length)];
//出力
System.out.println(len + "m!");
System.out.println(DAMAGE_MESSAGES[dmg+1]);
//長さの更新(0未満にはならない)
length = Math.max(length-len, 0);
//ダメージの更新(0未満にはならない)
damage = Math.max(damage+ dmg, 0);
//出力
System.out.println("残り:"+length +"m");
System.out.println("ダメージ:"+damage+"/5");
}
//結果出力
System.out.printf("%s%n",length == 0?"釣り上げることに成功した!":"糸が切れてしまった。。。失敗");
}
}
動作確認
実行してみよう、実行例のように遊べれば成功だ。
成功確率のシミュレーション
やってみてわかると思うがこのゲームはなかなか成功しない。では実際にはどのくらいの確率なのだろうか?1000回ほどシミュレーションして確率を求めてみよう。
import java.util.Random;
import java.util.Scanner;
public class FishingApp {
public static void main(String[] args) {
//巻取量テーブル
int[] LEN_ARR = { 20, 10, 10, 5, 5, 1, };
//ダメージテーブル
int[] DAMAGE_ARR = { -1, 0, 0, 0, 1, 1, 1, 1, 1, 1, };
//ダメージ表記
String[] DAMAGE_MESSAGES = { "Great!!!", "Good", "Bad" };
//ランダムインスタンス生成
Random rand = new Random();
//スキャナーインスタンス生成
Scanner sc = new Scanner(System.in);
//初期表示
System.out.println("釣りゲーム");
System.out.println("Press enter Key...");
int success = 0;
for (int i = 0; i < 1000; i++) {
int length = 100;
int damage = 2;
while (length > 0 && damage < 5) {
//sc.nextLine();
int len = LEN_ARR[rand.nextInt(LEN_ARR.length)];
int dmg = DAMAGE_ARR[rand.nextInt(DAMAGE_ARR.length)];
System.out.println(len + "m!");
System.out.println(DAMAGE_MESSAGES[dmg + 1]);
length = Math.max(length - len, 0);
damage = Math.max(damage + dmg, 0);
System.out.println("残り:" + length + "m");
System.out.println("ダメージ:" + damage + "/5");
}
if (length == 0) {
success++;
}
}
double successRatio = (success / 1000d) * 100;
//System.out.printf("%s%n",length == 0?"釣り上げることに成功した!":"糸が切れてしまった。。。失敗");
System.out.printf("%d/1000 成功率:%.1f%%%n", success, successRatio);
}
}
およそ、7~8%くらいの成功率であることがわかる。
なお実際のゲームではモードによっては巻取り量が1のところが11になる場合がある。値を変えて検証したところこの場合成功率は12~13%となりおよそ5%ほど成功確率がアップすることもわかった。。
リファクタリング(完成版)
最後にシミュレーション部分を削除して、もとのゲームに戻しリファクタリングを行う。
import java.util.Random;
import java.util.Scanner;
public class FishingApp {
public static void main(String[] args) {
//巻取量テーブル
final int[] LEN_ARR = { 20, 10, 10, 5, 5, 1, };
//ダメージテーブル
final int[] DAMAGE_ARR = { -1, 0, 0, 0, 1, 1, 1, 1, 1, 1, };
//ダメージ表記
final String[] DAMAGE_MESSAGES = { "Great!!!", "Good", "Bad" };
//スタート時の長さ
final int START_LENGTH = 100;
//maxダメージ
final int MAX_DAMAGE = 5;
//スタート時のダメージ
final int START_DAMAGE = 2;
//ランダムインスタンス生成
final Random rand = new Random();
//スキャナーインスタンス生成
final Scanner sc = new Scanner(System.in);
//残りの距離
int length = START_LENGTH;
//現在のダメージ
int damage = START_DAMAGE;
//初期表示
System.out.println("釣りゲーム");
System.out.println("Press enter Key...");
while (length > 0 && damage < MAX_DAMAGE) {
sc.nextLine();
int len = LEN_ARR[rand.nextInt(LEN_ARR.length)];
int dmg = DAMAGE_ARR[rand.nextInt(DAMAGE_ARR.length)];
System.out.println(len + "m!");
System.out.println(DAMAGE_MESSAGES[dmg + 1]);
length = Math.max(length - len, 0);
damage = Math.max(damage + dmg, 0);
System.out.println("残り:" + length + "m");
System.out.println("ダメージ:" + damage + "/" + MAX_DAMAGE);
}
System.out.printf("%s%n", length == 0 ? "釣り上げることに成功した!" : "糸が切れてしまった。。。失敗");
sc.close();
}
}
ポイント
ここではマジックナンバーの削除と、再代入の必要のない部分を定数化している。
最後に
今回はゲーセンにあるメダルゲームをシミュレーションしてみたが、スマホゲームなどにもプログラミングの練習になるお題がたくさん存在している。あなたがやっているゲームに出てくる仕組みも今回のようにプログラミングで再現してみるといいだろう。とても良い練習になる。
コメント