공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!! 포스팅 내용이 찾아주신 분들께 도움이 되길 바라며 더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^
|
안드로이드 퍼즐 게임을 간단하게 만들어 보았습니다.
제가 만든 퍼즐 게임은 오른쪽에 그림 원본이 있고
왼쪽에는 그림이 N * N으로 나눠져 있고, 한 칸은 비어있어서
이미지들을 이동시키면서 원본과 같게 맞추는 게임입니다.
Build.gradle(Module:app) 세팅
데이터 바인딩, 람다식을 사용하기 위한 컴파일 옵션을 추가했습니다.
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "al.tong.mon.scrambledtoappropriate"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
dataBinding {
enabled true
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
레이아웃은 메인 레이아웃과 RecyclerView에
아이템으로 들어갈 레이아웃이 있습니다.
activity_one.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".one.OneActivity">
<android.support.constraint.ConstraintLayout
android:background="@color/colorPrimary"
android:id="@+id/oneLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/correctImageView"
app:layout_constraintDimensionRatio="1:1"
android:layout_width="0dp"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:overScrollMode="never"
android:id="@+id/oneViews"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.constraint.ConstraintLayout>
<android.support.v7.widget.AppCompatImageView
android:src="@drawable/img1to9"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toEndOf="@+id/oneLayout"
app:layout_constraintDimensionRatio="1:1"
android:id="@+id/correctImageView"
android:layout_width="0dp"
android:layout_height="0dp" />
<android.support.v7.widget.AppCompatButton
android:id="@+id/restartBtn"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/correctImageView"
app:layout_constraintStart_toStartOf="@id/correctImageView"
android:text="다시하기"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="@android:color/black"
android:layout_width="0dp"
android:layout_height="96dp" />
</android.support.constraint.ConstraintLayout>
</layout>
item_one.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.CardView
android:id="@+id/itemCardView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.CardView>
</layout>
레이아웃은 간단합니다.
자바 클래스들.
CheckAvailable.java
각 이미지들이 이동할 수 있는지 판단하는 메소드입니다.
체크 조건은 9가지 입니다.
왼쪽위 | 오른쪽위 | 왼쪽아래 | 오른쪽 | 아래 | 상 | 하 | 좌 | 우 | 가운데
N x N 일 때 N이 몇 이든 다 체크가능합니다.
package al.tong.mon.scrambledtoappropriate.condition;
import java.util.Vector;
import al.tong.mon.scrambledtoappropriate.one.One;
public class CheckAvailable {
private Vector<One> mOne;
private int lengthPos;
private int spanPos;
private int span;
public CheckAvailable(Vector<One> ones, int span) {
this.mOne = ones;
this.lengthPos = ones.size() - 1;
this.spanPos = span - 1;
this.span = span;
}
public int check(int pos) {
int result;
if (pos == 0) { // left top
result = leftTop(pos);
} else if(pos == lengthPos - spanPos) { // left bottom
result = leftBottom(pos);
} else if(pos == spanPos) { //right top
result = rightTop(pos);
}else if(pos == lengthPos) { // right bottom
result = rightBottom(pos);
} else if(pos > 0 && pos < spanPos) { // top
result = top(pos);
} else if(pos > (lengthPos - spanPos) && pos < lengthPos) { // bottom
result = bottom(pos);
} else if(pos % span == 0 && pos != (lengthPos - spanPos)) { // left
result = left(pos);
} else if(pos % span == spanPos) { // right
result = right(pos);
} else {
result = center(pos);
}
return result;
}
private int leftTop(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else {
return -100;
}
}
private int top(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else {
return -100;
}
}
private int rightTop(int pos) {
if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else {
return -100;
}
}
private int left(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else {
return -100;
}
}
private int center(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
} else {
return -100;
}
}
private int right(int pos) {
if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos + span).isEmpty()) {
return pos + span;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
} else {
return -100;
}
}
private int leftBottom(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
}else {
return -100;
}
}
private int bottom(int pos) {
if(mOne.get(pos + 1).isEmpty()) {
return pos + 1;
} else if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
} else {
return -100;
}
}
private int rightBottom(int pos) {
if(mOne.get(pos - 1).isEmpty()) {
return pos - 1;
} else if(mOne.get(pos - span).isEmpty()) {
return pos - span;
} else {
return -100;
}
}
}
One.java ( DTO )
package al.tong.mon.scrambledtoappropriate.one;
public class One {
private int image;
private int tag;
private boolean empty;
One(int image, int tag, boolean empty) {
this.image = image;
this.tag = tag;
this.empty = empty;
}
public void setImage(int image) {
this.image = image;
}
public void setTag(int tag) {
this.tag = tag;
}
public void setEmpty(boolean empty) {
this.empty = empty;
}
public int getImage() {
return image;
}
public int getTag() {
return tag;
}
public boolean isEmpty() {
return empty;
}
}
[ 광고 보고 가시죠! ]
[ 감사합니다! ]
OneAdapter.java
package al.tong.mon.scrambledtoappropriate.one;
import android.app.Activity;
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import java.util.Vector;
import al.tong.mon.scrambledtoappropriate.R;
import al.tong.mon.scrambledtoappropriate.databinding.ItemOneBinding;
class OneAdapter extends RecyclerView.Adapter<OneAdapter.OneHolder> {
private Vector<One> mOne = new Vector<>();
private Activity activity;
private Context context;
private int width = 0, height = 0;
int[] imgs = new int[]{
R.drawable.img1, R.drawable.img2, R.drawable.img3,
R.drawable.img4, R.drawable.img5, R.drawable.img6,
R.drawable.img7, R.drawable.img8, R.drawable.img9
};
public OneAdapter(Activity activity) {
this.activity = activity;
this.context = activity.getApplicationContext();
}
@NonNull
@Override
public OneHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemOneBinding binding = ItemOneBinding.inflate(LayoutInflater.from(context), parent, false);
return new OneHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull OneHolder holder, int position) {
ItemOneBinding binding = holder.binding;
if (width != 0 && height != 0) {
binding.itemCardView.getLayoutParams().width = width;
binding.itemCardView.getLayoutParams().height = height;
}
One one = mOne.get(position);
int image = one.getImage();
binding.itemCardView.setBackgroundResource(image);
}
@Override
public int getItemCount() {
return mOne.size();
}
void update(Vector<One> ones) {
this.mOne = ones;
notifyDataSetChanged();
}
void setLength(int length) {
this.width = length;
this.height = length;
}
void change(int oldPos, int newPos) {
//6, 9
One oldOne = mOne.get(oldPos);
One newOne = mOne.get(newPos);
mOne.remove(newPos);
mOne.add(newPos, oldOne);
mOne.remove(oldPos);
mOne.add(oldPos, newOne);
notifyDataSetChanged();
}
Vector<One> currentOne() {
return this.mOne;
}
void finish(int pos) {
mOne.get(pos).setImage(imgs[pos]);
notifyItemChanged(pos);
}
class OneHolder extends RecyclerView.ViewHolder {
ItemOneBinding binding;
OneHolder(ItemOneBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}
OneActivity.java
package al.tong.mon.scrambledtoappropriate.one;
import android.databinding.DataBindingUtil;
import android.media.SoundPool;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Toast;
import java.util.Collections;
import java.util.Vector;
import al.tong.mon.scrambledtoappropriate.R;
import al.tong.mon.scrambledtoappropriate.condition.CheckAvailable;
import al.tong.mon.scrambledtoappropriate.databinding.ActivityOneBinding;
public class OneActivity extends AppCompatActivity {
ActivityOneBinding binding;
OneAdapter adapter;
CheckAvailable checkAvailable;
Vector<One> mOne;
int[] imgs;
private static final int N = 3;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_one);
// span과 이미지 갯수만 변경되면 모든 M x N 적용가능
GridLayoutManager layoutManager = new GridLayoutManager(this, N);
binding.oneViews.setLayoutManager(layoutManager);
adapter = new OneAdapter(this);
binding.oneViews.setAdapter(adapter);
mOne = new Vector<>();
imgs = new int[]{
R.drawable.img1, R.drawable.img2, R.drawable.img3,
R.drawable.img4, R.drawable.img5, R.drawable.img6,
R.drawable.img7, R.drawable.img8, R.drawable.img9
};
int randInt = (int) (Math.random() * imgs.length);
for (int i = 0; i < imgs.length; i++) {
if (i == randInt) {
mOne.add(new One(R.drawable.img_white, i, true));
} else {
mOne.add(new One(imgs[i], i, false));
}
}
Collections.shuffle(mOne);
adapter.update(mOne);
binding.oneViews.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int length = binding.oneViews.getWidth() / N;
adapter.setLength(length);
adapter.notifyDataSetChanged();
checkAvailable = new CheckAvailable(mOne, N);
binding.oneViews.addOnItemTouchListener(itemTouchListener);
binding.oneViews.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
binding.restartBtn.setOnClickListener(view -> recreate());
}
RecyclerView.OnItemTouchListener itemTouchListener = new RecyclerView.OnItemTouchListener() {
@Override
public boolean onInterceptTouchEvent(@NonNull RecyclerView parent, @NonNull MotionEvent evt) {
int action = evt.getAction();
if (action == MotionEvent.ACTION_UP) {
View child = parent.findChildViewUnder(evt.getX(), evt.getY());
assert child != null;
int pos = parent.getChildAdapterPosition(child);
int newPos = checkAvailable.check(pos);
if (newPos != -100) {
adapter.change(pos, newPos);
}
int good_job = 0;
for (int i = 0; i < mOne.size(); i++) {
if (i == mOne.get(i).getTag()) {
good_job++;
}
}
if (good_job == mOne.size()) {
Vector<One> one = adapter.currentOne();
for (int i = 0; i < one.size(); i++) {
boolean empty = one.get(i).isEmpty();
if (empty) {
adapter.finish(i);
binding.oneViews.removeOnItemTouchListener(itemTouchListener);
break;
}
}
Toast.makeText(OneActivity.this, "참 잘했어요.", Toast.LENGTH_SHORT).show();
}
}
return false;
}
@Override
public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
};
}
들어간 이미지들은 기본 제공하는 머터리얼디자인의 이미지들입니다.
그리고 이미지의 갯수가 저는 3 x 3으로 했는데 바꾸실 경우
OneActivity에서 N의 값을 변경하시면 됩니다.
소스는 깃허브에 공유해놓았습니다.
https://github.com/Parksunggyun/ScrambledToAppropriate
실행영상입니다.
이상입니다. 감사합니다.
※ 코드 수정 (2019.02.21.목)
슬라이드 퍼즐 맞추는 공식을 찾다가 보니,
위 포스팅까지한대로 따라하면 섞인 이미지를 맞출 수 있는 경우가 50%정도밖에 되지
않더라구요 ㅎㅎ 그래서 이미지들을 Collections.shuffle() 메소드로 섞지 않고
섞는 코드를 추가하였습니다.
=> 각 이미지들을 섞는 코드와 이동 가능 여부 체크 코드가 수정,추가되었습니다.
마찬가지로 깃허브에 공유해놓았습니다.
'안드로이드' 카테고리의 다른 글
안드로이드 Number picker 소프트키 안 뜨게 하게 disable soft keyboard (0) | 2019.02.19 |
---|---|
안드로이드 NumberPicker 텍스트 색상, 크기 변경하기. how to change text size and text color of NumberPicker (0) | 2019.02.19 |
안드로이드 x축, y축 회전 애니메이션 구현하기. (0) | 2019.02.14 |
안드로이드 같은 그림 찾기 게임을 만들어 보았습니다. Android simple game - Find the same picture game (2) | 2019.02.14 |
안드로이드 코틀린 익스텐션 사용하기. how to use kotlin extension in Android (0) | 2019.02.12 |