Good man James helped me learn die roll board game programming in Java,
and I applied it on Ladders and Snakes. Made one in Java. Now I'm learning to do it in Android.
Here's the question:
Making simple Ladders and Snakes game.
Can't use those in Google Play, they don't repeat move if 6 is thrown, and have no bounce around end square.
Made one in Java, works on Windows well. Now porting it to my phone.
Working with AbsoluteLayout to make it simple, because Java functions getX, getY, setX and setY work in px, not dp.
Scaling will learn after making this work.
Made views in XML, board is visible, ending banners invisible (for Victory, Defeat and Draw).
Buttons for Quit and Restart work well.
Tried to learn swing timer android replacement from online tutorials, but failed.
Roll button should call piece moves. Using new thread to avoid overwhelming UI.
Procedure there should be as follows:
- disable roll button
- roll die
- move Player piece square by square, usin playMove handler
- reposition Player piece if at ladder bottom or at snake head
- repeat if roll was 6
- if no, play for Device:
- roll die
- move Device piece square by square, using devMove handler
- reposition Device piece if at ladder bottom or at snake head
- repeat if roll was 6
- if not re-enable Roll button and go again when clicked (tapped) on it.
BUT: When I click on Roll, game crashes.
Can't find why. Tried few approaches, like, say, using PostDelayed...
Beg you, please give me direct example of possible solution.
First, I'm not the smartest guy in the world, and second,
didn't sleep much for 4-5 days and my understanding abilities are even lower.
Thanks in advance.
Here's my last attempt that still crashes:
// player 1 is Player, (s)he plays by single click on "Roll" button
// player 2 is Device, it plays as soon as Player finishes, no need to click "Roll" button
// whenever 6 is rolled another move is invoked automatically after completion of current one
import android.content.Intent;
import android.media.AudioManager;
import android.media.ToneGenerator;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AbsoluteLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import static java.lang.Math.random;
public class MainActivity extends AppCompatActivity {
AbsoluteLayout layout;
// Squares translation for Ladders and Snakes
final int[] posLS = new int[]{0, 1, 38, 3, 4, 5, 6, 14, 31, 9, 10, 11, 12, 13,
14, 26, 6, 17, 18, 19, 20, 42, 22, 23, 24, 25, 26, 27, 84, 29, 30, 31, 32, 33, 34, 35, 44,
37, 38, 39, 40, 41, 42, 43, 44, 45, 25, 47, 48, 11, 50, 67, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 19, 63, 60, 65, 66, 67, 68, 69, 70, 91, 72, 73, 53, 75, 76, 77, 98, 79, 80, 81, 82,
83, 84, 85, 86, 94, 88, 68, 90, 91, 88, 93, 94, 75, 96, 97, 98, 80, 100}; // Ladder/Snake
// X coordinates of squares on board
final int[] pcX = new int[]{210, 22, 58, 94, 130, 166, 202, 238, 274, 310, 346,
346, 310, 274, 238, 202, 166, 130, 94, 58, 22, 22, 58, 94, 130, 166, 202, 238, 274, 310, 346,
346, 310, 274, 238, 202, 166, 130, 94, 58, 22, 22, 58, 94, 130, 166, 202, 238, 274, 310,
346, 346, 310, 274, 238, 202, 166, 130, 94, 58, 22, 22, 58, 94, 130, 166, 202, 238, 274,
310, 346, 346, 310, 274, 238, 202, 166, 130, 94, 58, 22, 22, 58, 94, 130, 166, 202, 238,
274, 310, 346, 346, 310, 274, 238, 202, 166, 130, 94, 58, 22, 22, 58, 94, 130, 166, 202};
// Y cordinates of squares on board
final int[] pcY = new int[]{405, 346, 346, 346, 346, 346, 346, 346, 346, 346,
346, 310, 310, 310, 310, 310, 310, 310, 310, 310, 310, 274, 274, 274, 274, 274, 274, 274,
274, 274, 274, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238, 202, 202, 202, 202, 202,
202, 202, 202, 202, 202, 166, 166, 166, 166, 166, 166, 166, 166, 166, 166, 130, 130, 130,
130, 130, 130, 130, 130, 130, 130, 94, 94, 94, 94, 94, 94, 94, 94, 94, 94, 58, 58, 58, 58, 58,
58, 58, 58, 58, 58, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22};
ImageButton exit, restart, roll; // Buttons just below board
ImageView board, player, device, victory, defeat, draw; // Board, two pieces and three banners for game over
final int slowing = 200; // Delay between two piece jumps from square to square
int die = 0;
int playPos = 0; // At which square is Player
int playPosOld = 0;
int playPosNew = 0;
int devPos = 0; // At which square is Device
int devPosOld = 0;
int devPosNew = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = (AbsoluteLayout) findViewById(R.id.layout);
exit = (ImageButton) findViewById(R.id.exit);
restart = (ImageButton) findViewById(R.id.restart);
roll = (ImageButton) findViewById(R.id.roll);
board = (ImageView) findViewById(R.id.board);
player = (ImageView) findViewById(R.id.player);
device = (ImageView) findViewById(R.id.device);
draw = (ImageView) findViewById(R.id.draw);
defeat = (ImageView) findViewById(R.id.defeat);
victory = (ImageView) findViewById(R.id.victory);
draw.setVisibility(View.INVISIBLE);
defeat.setVisibility(View.INVISIBLE);
victory.setVisibility(View.INVISIBLE);
BoardLS();
}
public void BoardLS() {
exit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View ex) {
finish();
System.exit(0);
}
});
restart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View re) {
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
finish();
startActivity(i);
}
});
roll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View ro) {
new Thread(new goMove()).start();
}
});
}
public void beep() {
ToneGenerator beepSound = new ToneGenerator(AudioManager.STREAM_MUSIC, 60);
beepSound.startTone(ToneGenerator.TONE_PROP_BEEP, 75);
} // Short sound for each piece step
public void waiting() {
try { Thread.sleep(slowing); } catch(InterruptedException e) { }
} // Waits before next piece step
Handler playMove = new Handler() {
@Override
public void handleMessage(Message playMsg) {
player.setX(pcX[playPos]);
player.setY(pcY[playPos]);
}
}; // Player piece on-screen movement
Handler devMove = new Handler(){
@Override
public void handleMessage(Message devMsg) {
device.setX(pcX[devPos] + 10);
device.setY(pcY[devPos] + 10);
}
}; // Device piece on-screen movement
// This one rolls die, moves pieces, repeats roll and move if thrown was 6, shows banners for game over
// It was meant to work in separate thread to spare base one, but when "roll" button calls it, game crashes.
class goMove implements Runnable {
@Override
public void run() {
if (playPosOld < 100 && devPosOld < 100) {
roll.setEnabled(false);
do {
die = (int) (random() * 6 + 1);
playPos = playPosOld;
playPosNew = playPosOld + die;
for (playPos = playPosOld; playPos <= playPosNew; playPos++) {
waiting();
beep();
playMove.sendEmptyMessage(0);
}
playPos--;
if (playPos > 100) {
playPos = (100 - (playPos % 100));
}
} while (die == 6 && playPos < 100);
playPosOld = posLS[playPos];
playPos = playPosOld;
playMove.sendEmptyMessage(0);
do {
die = (int) (random() * 6 + 1);
devPos = devPosOld;
devPosNew = devPosOld + die;
for (devPos = devPosOld; devPos <= devPosNew; devPos++) {
waiting();
beep();
devMove.sendEmptyMessage(0);
}
devPos--;
if (devPos > 100) {
devPos = (100 - (devPos % 100));
}
} while (die == 6 && devPos < 100);
devPosOld = posLS[devPos];
devPos = devPosOld;
devMove.sendEmptyMessage(0);
}
if (playPosOld >= 100 && devPosOld < 100) {
victory.setVisibility(View.VISIBLE);
}
if (playPosOld < 100 && devPosOld >= 100) {
defeat.setVisibility(View.VISIBLE);
}
if (playPosOld >= 100 && devPosOld >= 100) {
draw.setVisibility(View.VISIBLE);
}
roll.setEnabled(true);
}
}
}
And here's the XML to show dimensions of elements. Board 400x400, pieces 22x22, and so on...
<?xml version="1.0" encoding="UTF-8"?>
<AbsoluteLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#202040"
tools:context="com.smilisav.boardls.MainActivity"
tools:layout_editor_absoluteX="0px"
tools:layout_editor_absoluteY="0px">
<ImageView
android:id="@+id/board"
android:layout_width="400px"
android:layout_height="400px"
app:srcCompat="@drawable/board400"
android:layout_x="0px"
android:layout_y="0px" />
<ImageButton
android:id="@+id/exit"
android:layout_width="99px"
android:layout_height="40px"
android:layout_x="0px"
android:layout_y="401px"
app:srcCompat="@drawable/exit" />
<ImageButton
android:id="@+id/restart"
android:layout_width="99px"
android:layout_height="40px"
android:layout_x="100px"
android:layout_y="401px"
app:srcCompat="@drawable/restart" />
<ImageButton
android:id="@+id/roll"
android:layout_width="149px"
android:layout_height="40px"
android:layout_x="250px"
android:layout_y="400px"
app:srcCompat="@drawable/roll" />
<ImageView
android:id="@+id/player"
android:layout_width="22px"
android:layout_height="22px"
android:layout_x="210px"
android:layout_y="405px"
app:srcCompat="@drawable/purple"/>
<ImageView
android:id="@+id/device"
android:layout_width="22px"
android:layout_height="22px"
android:layout_x="220px"
android:layout_y="415px"
app:srcCompat="@drawable/yellow"/>
<ImageView
android:id="@+id/draw"
android:layout_width="200px"
android:layout_height="100px"
android:layout_x="100px"
android:layout_y="150px"
app:srcCompat="@drawable/draw"/>
<ImageView
android:id="@+id/defeat"
android:layout_width="200px"
android:layout_height="100px"
android:layout_x="100px"
android:layout_y="150px"
app:srcCompat="@drawable/defeat"/>
<ImageView
android:id="@+id/victory"
android:layout_width="200px"
android:layout_height="100px"
android:layout_x="100px"
android:layout_y="150px"
app:srcCompat="@drawable/victory"/>
</AbsoluteLayout>