diff --git a/Labyrinth/.idea/misc.xml b/Labyrinth/.idea/misc.xml index 5d19981..ba7052b 100644 --- a/Labyrinth/.idea/misc.xml +++ b/Labyrinth/.idea/misc.xml @@ -1,8 +1,5 @@ - - - - - - - - - - - - - - + diff --git a/Labyrinth/app/build.gradle b/Labyrinth/app/build.gradle index c188ad9..694ce44 100644 --- a/Labyrinth/app/build.gradle +++ b/Labyrinth/app/build.gradle @@ -10,6 +10,7 @@ android { versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + vectorDrawables.useSupportLibrary = true } buildTypes { release { @@ -26,6 +27,11 @@ dependencies { }) compile 'com.android.support:appcompat-v7:26.+' compile 'com.android.support.constraint:constraint-layout:1.0.2' + compile 'com.google.code.gson:gson:2.8.2' + compile 'com.android.support:design:26.+' + compile 'com.android.support:support-vector-drawable:26.+' testCompile 'junit:junit:4.12' + implementation 'com.google.android.gms:play-services-games:11.6.0' + implementation 'com.google.android.gms:play-services-auth:11.6.0' } apply plugin: 'com.android.application' \ No newline at end of file diff --git a/Labyrinth/app/release/output.json b/Labyrinth/app/release/output.json new file mode 100644 index 0000000..c8dc8df --- /dev/null +++ b/Labyrinth/app/release/output.json @@ -0,0 +1 @@ +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1},"path":"app-release.apk","properties":{"packageId":"ru.spbau.labyrinth","split":"","minSdkVersion":"16"}}] \ No newline at end of file diff --git a/Labyrinth/app/src/androidTest/java/ru/spbau/labyrinth/ExampleInstrumentedTest.java b/Labyrinth/app/src/androidTest/java/ru/spbau/labyrinth/ExampleInstrumentedTest.java index 1ffad9d..bcbf7de 100644 --- a/Labyrinth/app/src/androidTest/java/ru/spbau/labyrinth/ExampleInstrumentedTest.java +++ b/Labyrinth/app/src/androidTest/java/ru/spbau/labyrinth/ExampleInstrumentedTest.java @@ -7,7 +7,7 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; /** * Instrumentation test, which will execute on an Android device. diff --git a/Labyrinth/app/src/main/AndroidManifest.xml b/Labyrinth/app/src/main/AndroidManifest.xml index 53a9088..dae02ac 100644 --- a/Labyrinth/app/src/main/AndroidManifest.xml +++ b/Labyrinth/app/src/main/AndroidManifest.xml @@ -2,21 +2,39 @@ - - - - + + + + + + + - - - - - + + + + + + + + + + + \ No newline at end of file diff --git a/Labyrinth/app/src/main/ic_launcher-web.png b/Labyrinth/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..504465e Binary files /dev/null and b/Labyrinth/app/src/main/ic_launcher-web.png differ diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EditorActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EditorActivity.java new file mode 100644 index 0000000..0793aa1 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EditorActivity.java @@ -0,0 +1,140 @@ +package ru.spbau.labyrinth; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.view.MotionEvent; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Toast; + +import ru.spbau.labyrinth.customviews.EditFieldView; +import ru.spbau.labyrinth.customviews.OuterScrollView; +import ru.spbau.labyrinth.db.DBHelper; +import ru.spbau.labyrinth.model.field.Field; + +public class EditorActivity extends AppCompatActivity implements View.OnClickListener { + private static final int MAZE_REQUEST = 2; + static DBHelper dbHelper; + static EditFieldView editFieldView; + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == MAZE_REQUEST) { + if (data == null || resultCode != RESULT_OK) { + return; + } + String maze = data.getStringExtra("maze"); + if (maze != null) { + setEditFieldView(maze); + checkField(); + } + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_editor); + + final OuterScrollView outerScrollView = findViewById(R.id.outerScroll); + final HorizontalScrollView horizontalScrollView = findViewById(R.id.horizontalScroll); + editFieldView = findViewById(R.id.fieldView); + + outerScrollView.post(new Runnable() { + public void run() { + outerScrollView.scrollTo(0, 650); + } + }); + horizontalScrollView.post(new Runnable() { + public void run() { + horizontalScrollView.scrollTo(650, 0); + } + }); + + Button saveButton = findViewById(R.id.saveButton); + Button loadButton = findViewById(R.id.loadButton); + Button checkButton = findViewById(R.id.checkButton); + + saveButton.setOnClickListener(this); + loadButton.setOnClickListener(this); + checkButton.setOnClickListener(this); + + dbHelper = new DBHelper(this); + } + + private void saveMaze(final Field field) { + if (!checkField()) + return; + AlertDialog.Builder builder = new AlertDialog.Builder(this); + final EditText input = new EditText(this); + input.setHint("Maze name..."); + LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT); + input.setLayoutParams(lp); + builder.setView(input); + builder.setPositiveButton("SAVE", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + String name = input.getText().toString(); + if (!name.equals("")) { + dbHelper.saveField(field, name); + } + } + }); + builder.show(); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.saveButton: { + saveMaze(editFieldView.getField()); + break; + } + case R.id.loadButton: { + Intent intent = new Intent(EditorActivity.this, LevelSelectActivity.class); + startActivityForResult(intent, MAZE_REQUEST); + break; + } + case R.id.checkButton: { + checkField(); + break; + } + } + dbHelper.close(); + } + + @Override + protected void onResume() { + super.onResume(); + checkField(); + } + + public boolean checkField() { + editFieldView = findViewById(R.id.fieldView); + final ImageView backgroundImageView = findViewById(R.id.background); + + Field.ErrorType error = editFieldView.getField().isCorrect(); + if (error == Field.ErrorType.NO_ERROR) { + backgroundImageView.setImageResource(R.drawable.labyrinth_green); + return true; + } else { + Toast.makeText(getApplicationContext(), error.getMessage(), Toast.LENGTH_SHORT).show(); + backgroundImageView.setImageResource(R.drawable.labyrinth_red); + return false; + } + } + + public static void setEditFieldView(String json) { + editFieldView.setField(Field.deserialize(json)); + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EndGameActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EndGameActivity.java new file mode 100644 index 0000000..c5d2a7c --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/EndGameActivity.java @@ -0,0 +1,41 @@ +package ru.spbau.labyrinth; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +public class EndGameActivity extends AppCompatActivity { + private final int[] backgrounds = { + R.drawable.labyrinth_red, + R.drawable.labyrinth_blue, + R.drawable.labyrinth_green, + R.drawable.labyrinth_yellow}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_endgame); + Intent intent = getIntent(); + int winner = intent.getIntExtra("winnerId", 0); + String name = intent.getStringExtra("winnerName"); + + ImageView background = findViewById(R.id.background); + background.setImageResource(backgrounds[winner]); + TextView endTextView = findViewById(R.id.endTextView); + + endTextView.setText("GAME OVER\nWinner:\n" + name); + + Button menuButton = findViewById(R.id.menuButton); + menuButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + finish(); + } + }); + } + +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/GameActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/GameActivity.java new file mode 100644 index 0000000..fe76043 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/GameActivity.java @@ -0,0 +1,173 @@ +package ru.spbau.labyrinth; + +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; +import android.widget.HorizontalScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import ru.spbau.labyrinth.customviews.DirectionChooseView; +import ru.spbau.labyrinth.customviews.OuterScrollView; +import ru.spbau.labyrinth.customviews.PlayerFieldView; +import ru.spbau.labyrinth.model.GameState; +import ru.spbau.labyrinth.model.Log; +import ru.spbau.labyrinth.model.Model; + +public abstract class GameActivity extends AppCompatActivity { + protected GameState state; + + protected int currentDrawnPlayerNum; + protected final static int[] backgrounds = { + R.drawable.labyrinth_red, + R.drawable.labyrinth_blue, + R.drawable.labyrinth_green, + R.drawable.labyrinth_yellow}; + + protected void updatePlayerView(boolean scroll) { + printTreasureOwner(); + final OuterScrollView outerScrollView = findViewById(R.id.outerScroll); + final HorizontalScrollView horizontalScrollView = findViewById(R.id.horizontalScroll); + final DirectionChooseView moveDirectionChooseView = findViewById(R.id.moveDirView); + final DirectionChooseView shootDirectionChooseView = findViewById(R.id.shootDirView); + final PlayerFieldView fieldView = findViewById(R.id.fieldView); + final TextView cartridgesTextView = findViewById(R.id.cartridgesTextView); + final TextView currentPlayerNameTextView = findViewById(R.id.currentPlayerName); + + Model.Player player = state.getPlayers()[currentDrawnPlayerNum]; + cartridgesTextView.setText(Integer.toString(player.getCartridgesCnt())); + currentPlayerNameTextView.setText(player.getName()); + + moveDirectionChooseView.setPlayerNum(state.getCurrentPlayerNum()); + shootDirectionChooseView.setPlayerNum(state.getCurrentPlayerNum()); + if (currentDrawnPlayerNum == state.getCurrentPlayerNum()) { + cartridgesTextView.setTypeface(null, Typeface.BOLD); + currentPlayerNameTextView.setTypeface(null, Typeface.BOLD); + fieldView.updatePlayer(player, moveDirectionChooseView.getDirection(), shootDirectionChooseView.getDirection()); + } else { + cartridgesTextView.setTypeface(null, Typeface.NORMAL); + currentPlayerNameTextView.setTypeface(null, Typeface.NORMAL); + fieldView.updatePlayer(player, Model.Direction.NONE, Model.Direction.NONE); + } + int toScrollX = 3 + (player.getX() - player.getInitialX()); + int toScrollY = 3 + (player.getY() - player.getInitialY()); + if (scroll) { + outerScrollView.scrollTo(0, toScrollY * 200 + 50); + horizontalScrollView.scrollTo(toScrollX * 200 + 50, 0); + } + } + + protected void finishGame(int winner) { + Intent intent = new Intent(this, EndGameActivity.class); + intent.putExtra("winnerName", state.getPlayers()[winner].getName()); + intent.putExtra("winnerId", winner); + finish(); + startActivity(intent); + } + + private void printTreasureOwner() { + final TextView treasureTextView = findViewById(R.id.treasureTextView); + treasureTextView.setText(state.getTreasureOwner() + " has the treasure"); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_game); + + final OuterScrollView outerScrollView = findViewById(R.id.outerScroll); + final HorizontalScrollView horizontalScrollView = findViewById(R.id.horizontalScroll); + //outerScrollView.horizontalScrollView = horizontalScrollView; + final DirectionChooseView moveDirectionChooseView = findViewById(R.id.moveDirView); + final DirectionChooseView shootDirectionChooseView = findViewById(R.id.shootDirView); + final Button nextTurnButton = findViewById(R.id.nextTurnButton); + + initializeGameState(); + + updatePlayerView(true); + + moveDirectionChooseView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + updatePlayerView(false); + } + }); + + shootDirectionChooseView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + updatePlayerView(false); + } + }); + + nextTurnButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Model.Turn turn = new Model.Turn(moveDirectionChooseView.getDirection(), + shootDirectionChooseView.getDirection(), state.getCurrentPlayerNum()); + + processNextTurn(turn); + + updatePlayerView(true); + } + }); + + Button logButton = findViewById(R.id.logButton); + logButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (GameActivity.this instanceof OnlineGameActivity) { + Toast.makeText(GameActivity.this, "Online", Toast.LENGTH_LONG).show(); + OnlineGameActivity onlineGameActivity = (OnlineGameActivity) GameActivity.this; + onlineGameActivity.updateMatch(); + } + Intent intent = new Intent(GameActivity.this, LogActivity.class); + intent.putExtra("log", Log.serialize(state.log)); + startActivity(intent); + } + }); + + Button prevPlayerButton = findViewById(R.id.prevPlayerButton); + prevPlayerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + currentDrawnPlayerNum--; + if (currentDrawnPlayerNum < 0) { + currentDrawnPlayerNum = state.playerNum - 1; + } + + updatePlayerView(true); + } + }); + + Button nextPlayerButton = findViewById(R.id.nextPlayerButton); + nextPlayerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + currentDrawnPlayerNum++; + if (currentDrawnPlayerNum >= state.playerNum) { + currentDrawnPlayerNum = 0; + } + + updatePlayerView(true); + } + }); + + outerScrollView.post(new Runnable() { + public void run() { + outerScrollView.scrollTo(0, 650); + } + }); + horizontalScrollView.post(new Runnable() { + public void run() { + horizontalScrollView.scrollTo(650, 0); + } + }); + } + + protected abstract void initializeGameState(); + protected abstract void processNextTurn(Model.Turn turn); +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/InitActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/InitActivity.java new file mode 100644 index 0000000..6ca7b8f --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/InitActivity.java @@ -0,0 +1,131 @@ +package ru.spbau.labyrinth; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; + +public class InitActivity extends AppCompatActivity { + private static final int MAZE_REQUEST = 2; + private int playerNum = MIN_PLAYERS; + private static final int MAX_PLAYERS = 4; + private static final int MIN_PLAYERS = 2; + private EditText[] playerEdits; + private String[] names; + + private String[] getNames() { + names = new String[playerNum]; + for (int i = 0; i < playerNum; i++) { + names[i] = playerEdits[i].getText().toString(); + + if (names[i].equals("")) { + return null; + } + + for (int j = 0; j < i; j++) { + if (names[i].equals(names[j])) { + return null; + } + } + } + return names; + } + + private EditText constructEditText(int num) { + EditText editText = new EditText(this.getBaseContext()); + editText.setHint("Name..."); + editText.setText("Player " + Integer.toString(num)); + editText.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT)); + editText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); + return editText; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_init); + final Button addButton = findViewById(R.id.addPlayerButton); + final Button deleteButton = findViewById(R.id.deletePlayerButton); + final Button startButton = findViewById(R.id.startButton); + + final LinearLayout playersLayout = findViewById(R.id.playersLayout); + + playerEdits = new EditText[MAX_PLAYERS]; + for (int i = 0; i < MAX_PLAYERS; i++) { + playerEdits[i] = constructEditText(i + 1); + if (i > MIN_PLAYERS - 1) { + playerEdits[i].setVisibility(View.GONE); + } + playersLayout.addView(playerEdits[i]); + } + + addButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (playerNum < MAX_PLAYERS) { + playerEdits[playerNum].setVisibility(View.VISIBLE); + playerEdits[playerNum].setText("Player " + Integer.toString(playerNum + 1)); + playerNum++; + deleteButton.setVisibility(View.VISIBLE); + } + if (playerNum == MAX_PLAYERS) { + addButton.setVisibility(View.GONE); + } + } + }); + + deleteButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (playerNum > MIN_PLAYERS) { + playerNum--; + playerEdits[playerNum].setText(""); + playerEdits[playerNum].setVisibility(View.GONE); + addButton.setVisibility(View.VISIBLE); + } + if (playerNum == MIN_PLAYERS) { + deleteButton.setVisibility(View.GONE); + } + } + }); + + startButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String[] names = getNames(); + if (names == null) { + return; + } + Intent intent = new Intent(InitActivity.this, LevelSelectActivity.class); + startActivityForResult(intent, MAZE_REQUEST); + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == MAZE_REQUEST) { + if (data == null || resultCode != RESULT_OK) { + return; + } + String maze = data.getStringExtra("maze"); + if (maze == null) { + return; + } + + data.putExtra("playerNum", playerNum); + for (int i = 0; i < playerNum; i++) { + data.putExtra("player"+Integer.toString(i), names[i]); + } + + setResult(RESULT_OK, data); + finish(); + } + + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LevelSelectActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LevelSelectActivity.java new file mode 100644 index 0000000..38b9290 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LevelSelectActivity.java @@ -0,0 +1,62 @@ +package ru.spbau.labyrinth; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +import java.util.ArrayList; + +import ru.spbau.labyrinth.db.DBHelper; + +public class LevelSelectActivity extends AppCompatActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_level_select); + final DBHelper dbHelper = new DBHelper(this); + + ListView listView = findViewById(R.id.listView); + final ArrayList mazes = new ArrayList<>(); + mazes.addAll(dbHelper.getAllSavedMazesNames()); + final ArrayList ids = new ArrayList<>(); + ids.addAll(dbHelper.getAllSavedMazesIds()); + final ArrayAdapter arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, mazes); + listView.setAdapter(arrayAdapter); + listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + Intent intent = new Intent(); + intent.putExtra("maze", dbHelper.findMazeById(ids.get(i))); + setResult(RESULT_OK, intent); + LevelSelectActivity.this.finish(); + } + }); + listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView adapterView, View view, final int i, long l) { + AlertDialog.Builder builder = new AlertDialog.Builder(LevelSelectActivity.this); + builder.setMessage("Delete maze?"); + builder.setPositiveButton("YES", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int j) { + dbHelper.deleteMazeById(ids.get(i)); + mazes.clear(); + ids.clear(); + mazes.addAll(dbHelper.getAllSavedMazesNames()); + ids.addAll(dbHelper.getAllSavedMazesIds()); + arrayAdapter.notifyDataSetChanged(); + } + }); + builder.show(); + return true; + } + }); + + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LocalGameActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LocalGameActivity.java new file mode 100644 index 0000000..3d64872 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LocalGameActivity.java @@ -0,0 +1,95 @@ +package ru.spbau.labyrinth; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.widget.ImageView; +import android.widget.Toast; + +import ru.spbau.labyrinth.customviews.DirectionChooseView; +import ru.spbau.labyrinth.model.GameState; +import ru.spbau.labyrinth.model.Model; +import ru.spbau.labyrinth.model.field.Field; + +import static android.preference.PreferenceManager.getDefaultSharedPreferences; + +public class LocalGameActivity extends GameActivity { + private final static String PREFS_NAME = "LocalSave"; + + @Override + protected void finishGame(int winner) { + super.finishGame(winner); + clearSave(); + } + + private void clearSave() { + SharedPreferences savedGame = getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = savedGame.edit(); + editor.putBoolean("saved", false); + editor.commit(); + } + + @Override + protected void initializeGameState() { + Intent intent = getIntent(); + + if (intent.getBooleanExtra("isNewGame", true)) { + int playerNum = intent.getIntExtra("playerNum", 0); + String[] names = new String[playerNum]; + for (int i = 0; i < playerNum; i++) { + names[i] = intent.getStringExtra("player" + Integer.toString(i)); + } + String maze = intent.getStringExtra("maze"); + if (maze == null) { + state = new GameState(names); + } else { + state = new GameState(names, Field.deserialize(maze)); + } + } else { + state = GameState.deserialize( + getDefaultSharedPreferences(getApplicationContext()) + .getString("gameState", null)); + } + if (state == null) { + finish(); + return; + } + currentDrawnPlayerNum = state.getCurrentPlayerNum(); + + final ImageView backgroundImageView = findViewById(R.id.background); + backgroundImageView.setImageResource( + backgrounds[state.getCurrentPlayerNum()]); + } + + @Override + protected void processNextTurn(Model.Turn turn) { + final DirectionChooseView moveDirectionChooseView = findViewById(R.id.moveDirView); + final DirectionChooseView shootDirectionChooseView = findViewById(R.id.shootDirView); + final ImageView backgroundImageView = findViewById(R.id.background); + + moveDirectionChooseView.resetDirection(); + shootDirectionChooseView.resetDirection(); + + int turnResult = state.updateTurn(turn); + if (turnResult != -1) { + finishGame(turnResult); + } + + backgroundImageView.setImageResource(backgrounds[state.getCurrentPlayerNum()]); + currentDrawnPlayerNum = state.getCurrentPlayerNum(); + + } + + @Override + protected void onPause(){ + super.onPause(); + + SharedPreferences savedGame = getDefaultSharedPreferences(getApplicationContext()); + SharedPreferences.Editor editor = savedGame.edit(); + + editor.putString("gameState", state.serialize()); + editor.putBoolean("saved", true); + + editor.commit(); + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LogActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LogActivity.java index e3dabf2..6c8d817 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LogActivity.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/LogActivity.java @@ -1,7 +1,8 @@ package ru.spbau.labyrinth; -import android.support.v7.app.AppCompatActivity; +import android.content.Intent; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import android.view.Gravity; import android.view.View; import android.widget.Button; @@ -9,71 +10,55 @@ import android.widget.TableRow; import android.widget.TextView; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; -import ru.spbau.labyrinth.model.Model.*; +import ru.spbau.labyrinth.model.Log; +import ru.spbau.labyrinth.model.Model.Direction; +import ru.spbau.labyrinth.model.Model.Turn; public class LogActivity extends AppCompatActivity { - class Move { - Direction moveDirection; - Direction shootDirection; - - public Move(Direction moveDirection, - Direction shootDirection) { - this.moveDirection = moveDirection; - this.shootDirection = shootDirection; - } - } - - class Log { + private final static int playerColors[] = new int[]{ + R.color.player_red, + R.color.player_blue, + R.color.player_green, + R.color.player_yellow + }; - public final int players; - - private List moves; - - public Log(int players) { - this.players = players; - moves = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - Move[] turn = new Move[players]; - for (int j = 0; j < players; j++) { - turn[j] = new Move(Direction.UP, - Direction.UP); - } - moves.add(turn); - } - } - - public Move getMove(int turn, int player) { - return moves.get(turn)[player]; - } - - public int getTurns() { - return moves.size(); - } - } - - Log gameLog; + private final static Map dirChars = new HashMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); setContentView(R.layout.activity_log); - TableLayout table = (TableLayout) findViewById(R.id.table); - gameLog = new Log(3); - for (int i = 0; i < gameLog.getTurns(); i++) { + TableLayout table = findViewById(R.id.table); + + Intent intent = getIntent(); + String json = intent.getStringExtra("log"); + Log gameLog = Log.deserialize(json); + + dirChars.put(Direction.UP, '↑'); + dirChars.put(Direction.DOWN, '↓'); + dirChars.put(Direction.RIGHT, '→'); + dirChars.put(Direction.LEFT, '←'); + dirChars.put(Direction.NONE, '⋅'); + + for (int i = 0; i < gameLog.getTurnsNum(); i++) { TableRow row = new TableRow(this); - for (int j = 0; j < gameLog.players; j++) { + for (int j = 0; j < gameLog.getPlayerNum(); j++) { TextView textView = new TextView(this); - Move move = gameLog.getMove(i, j); - textView.setText(String.format("s:%s, m:%s", move.moveDirection.name(), move.shootDirection.name())); + Turn turn = gameLog.getTurn(i, j); + textView.setTextSize(20); + textView.setText(String.format("M: %s, S: %s", dirChars.get(turn.getMoveDir()), dirChars.get(turn.getShootDir()))); textView.setGravity(Gravity.CENTER_HORIZONTAL); + textView.setBackgroundResource(playerColors[j]); row.addView(textView); } table.addView(row); } - Button backButton = (Button) findViewById(R.id.backButton); + + Button backButton = findViewById(R.id.backButton); backButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/MainActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/MainActivity.java deleted file mode 100644 index b425eac..0000000 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/MainActivity.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.spbau.labyrinth; - -import android.content.Intent; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; -import android.widget.Button; -import android.widget.HorizontalScrollView; -import android.widget.TextView; - -import ru.spbau.labyrinth.model.Model; - -public class MainActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - final OuterScrollView outerScrollView = (OuterScrollView) findViewById(R.id.outerScroll); - final HorizontalScrollView horizontalScrollView = (HorizontalScrollView) findViewById(R.id.horizontalScroll); - final DirectionChooseView moveDirectionChooseView = (DirectionChooseView) findViewById(R.id.moveDirView); - final DirectionChooseView shootDirectionChooseView = (DirectionChooseView) findViewById(R.id.shootDirView); - outerScrollView.horizontalScrollView = horizontalScrollView; - - Button nextTurnButton = (Button) findViewById(R.id.nextTurnButton); - final FieldView fieldView = (FieldView) findViewById(R.id.fieldView); - final TextView textView = (TextView) findViewById(R.id.textView2); - final Model model = new Model(); - fieldView.updatePlayer(model.demoInit()); - - nextTurnButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Model.Player player = model.processTurn(moveDirectionChooseView.getDirection(), shootDirectionChooseView.getDirection()); - fieldView.updatePlayer(player); - textView.setText(Integer.toString(player.getCartridgesCnt())); - moveDirectionChooseView.resetDirection(); - shootDirectionChooseView.resetDirection(); - } - }); - - Button logButton = (Button) findViewById(R.id.logButton); - logButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, LogActivity.class); - startActivity(intent); - } - }); - - outerScrollView.post(new Runnable() { - public void run() { - outerScrollView.scrollTo(0, 650); - } - }); - horizontalScrollView.post(new Runnable() { - public void run() { - horizontalScrollView.scrollTo(650, 0); - } - }); - - } - -} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/OnlineGameActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/OnlineGameActivity.java new file mode 100644 index 0000000..41d6209 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/OnlineGameActivity.java @@ -0,0 +1,131 @@ +package ru.spbau.labyrinth; + +import android.content.Intent; +import android.os.Handler; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.Toast; + +import ru.spbau.labyrinth.customviews.DirectionChooseView; +import ru.spbau.labyrinth.model.GameState; +import ru.spbau.labyrinth.model.Model; +import ru.spbau.labyrinth.networkMultiplayer.MultiplayerMatch; + +public class OnlineGameActivity extends GameActivity { + private final static String PREFS_NAME = "LocalSave"; + private MultiplayerMatch match = MultiplayerMatch.getInstance(); + private Thread checkingThread; + + @Override + protected void finishGame(int winner) { + super.finishGame(winner); + } + + @Override + protected void initializeGameState() { + match.onInitiateMatch(match.turnBasedMatch); + if (match.turnBasedMatch.getData() == null) { + state = new GameState(new String[match.turnBasedMatch.getParticipantIds().size()]); + } else { + state = GameState.deserialize(new String(match.turnBasedMatch.getData())); + } + + if (state == null) { + finish(); + return; + } + + currentDrawnPlayerNum = match.getPlayersNumber(); + if (currentDrawnPlayerNum == -1) { + finish(); + return; + } + + final ImageView backgroundImageView = findViewById(R.id.background); + backgroundImageView.setImageResource(backgrounds[state.getCurrentPlayerNum()]); + + final Handler myHandler = new Handler(); + final Button nextTurnButton = findViewById(R.id.nextTurnButton); + checkingThread = new Thread(new Runnable() { + @Override + public void run() { + Toast.makeText(OnlineGameActivity.this, "Starting thread.", Toast.LENGTH_LONG).show(); + try { + while (true) { + if (!Thread.interrupted()) { + synchronized (OnlineGameActivity.this) { + final boolean isPlayersTurn = OnlineGameActivity.this.match.isPlayersTurn(); + myHandler.post(new Runnable() { + @Override + public void run() { + if (isPlayersTurn) { + nextTurnButton.setEnabled(true); + } else { + nextTurnButton.setEnabled(false); + } + if (OnlineGameActivity.this.match.turnBasedMatch.getData() != null) { + updateMatch(); + } + + //Toast.makeText(OnlineGameActivity.this, needToUpdateField ? "Up to date" : "Need to update", Toast.LENGTH_SHORT).show(); + } + }); + Thread.sleep(1000); + } + } else { + break; + } + } + } catch (InterruptedException e){ + } + } + }); + Toast.makeText(OnlineGameActivity.this, "Starting thread.", Toast.LENGTH_LONG).show(); + + checkingThread.start(); + } + + @Override + protected void processNextTurn(Model.Turn turn) { + if (!match.isPlayersTurn()) { + return; + } + + final DirectionChooseView moveDirectionChooseView = findViewById(R.id.moveDirView); + final DirectionChooseView shootDirectionChooseView = findViewById(R.id.shootDirView); + + moveDirectionChooseView.resetDirection(); + shootDirectionChooseView.resetDirection(); + + int turnResult = state.updateTurn(turn); + if (turnResult != -1) { + match.finish(); + return; + } + match.sendData(state.serialize().getBytes()); + findViewById(R.id.nextTurnButton).setEnabled(false); + } + + @Override + public void onBackPressed() { + checkingThread.interrupt(); + try { + checkingThread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + super.onBackPressed(); + } + + @Override + protected void onPause(){ + super.onPause(); + checkingThread.interrupt(); + } + + public void updateMatch() { + match.update(); + state = GameState.deserialize(new String(match.turnBasedMatch.getData())); + updatePlayerView(true); + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/StartActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/StartActivity.java new file mode 100644 index 0000000..84f2263 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/StartActivity.java @@ -0,0 +1,85 @@ +package ru.spbau.labyrinth; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; + +import ru.spbau.labyrinth.networkMultiplayer.MultiplayerActivity; + +import static android.preference.PreferenceManager.getDefaultSharedPreferences; + +public class StartActivity extends AppCompatActivity { + private static final int PLAYERNAMES_REQUEST = 1; + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PLAYERNAMES_REQUEST) { + if (data == null || resultCode != RESULT_OK) { + return; + } + Intent intent = new Intent(StartActivity.this, LocalGameActivity.class); + intent.putExtras(data); + intent.putExtra("isNewGame", true); + startActivity(intent); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_start); + Button launchButton = findViewById(R.id.startButton); + launchButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(getApplicationContext(), InitActivity.class); + startActivityForResult(intent, PLAYERNAMES_REQUEST); + } + }); + Button loadButton = findViewById(R.id.loadButton); + + loadButton.setEnabled(isSavedGame()); + + loadButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(StartActivity.this, LocalGameActivity.class); + intent.putExtra("isNewGame", false); + startActivity(intent); + } + }); + Button editorButton = findViewById(R.id.editorButton); + editorButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(StartActivity.this, EditorActivity.class); + startActivity(intent); + } + }); + Button multiplayerButton = findViewById(R.id.multiplayerButton); + multiplayerButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + Intent intent = new Intent(StartActivity.this, MultiplayerActivity.class); + startActivity(intent); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + final Button loadButton = findViewById(R.id.loadButton); + loadButton.setEnabled(isSavedGame()); + } + + private boolean isSavedGame() { + SharedPreferences savedGame = getDefaultSharedPreferences(getApplicationContext()); + boolean b = savedGame.getBoolean("saved", false); + return savedGame.getBoolean("saved", false); + } + +} \ No newline at end of file diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/DirectionChooseView.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/DirectionChooseView.java similarity index 87% rename from Labyrinth/app/src/main/java/ru/spbau/labyrinth/DirectionChooseView.java rename to Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/DirectionChooseView.java index 2cfbd9e..f9bf438 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/DirectionChooseView.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/DirectionChooseView.java @@ -1,24 +1,27 @@ -package ru.spbau.labyrinth; +package ru.spbau.labyrinth.customviews; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Path; -import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; + import java.util.EnumMap; import java.util.Map; -import ru.spbau.labyrinth.model.Model.*; +import ru.spbau.labyrinth.model.Model.Direction; public class DirectionChooseView extends View { private Map positionX; private Map positionY; - + private final static int playerColors[] = new int[]{Color.RED, + Color.BLUE, + Color.GREEN, + Color.YELLOW}; + private int playerNum = 0; private Direction chosen; private Paint paint; @@ -68,7 +71,7 @@ protected void onDraw(Canvas canvas) { for (Direction dir : Direction.values()) { if (dir == chosen) { - paint.setColor(Color.RED); + paint.setColor(playerColors[playerNum]); } else { paint.setColor(Color.GRAY); } @@ -104,4 +107,8 @@ public boolean onTouchEvent(MotionEvent event) { invalidate(); return super.onTouchEvent(event); } + + public void setPlayerNum(int currentPlayerNum) { + playerNum = currentPlayerNum; + } } diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/EditFieldView.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/EditFieldView.java new file mode 100644 index 0000000..6eaf5d4 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/EditFieldView.java @@ -0,0 +1,205 @@ +package ru.spbau.labyrinth.customviews; + +import android.app.AlertDialog; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.support.annotation.Nullable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.widget.ImageButton; + +import ru.spbau.labyrinth.R; +import ru.spbau.labyrinth.model.field.Field; +import ru.spbau.labyrinth.model.field.Field.State; + + +public class EditFieldView extends FieldView { + private static final int PRECISION = 35; + protected static final int MAZE_OFFSET_X = 3; + protected static final int MAZE_OFFSET_Y = 3; + public EditFieldView(Context context) { + super(context); + init(); + } + + private void init() { + offsetX = MAZE_OFFSET_X; + offsetY = MAZE_OFFSET_Y; + int size = 3; + field = new Field(size); + for (int i = 0; i < size; i++) { + field.addBorderX(0, i); + field.addBorderX(size, i); + field.addBorderY(i, 0); + field.addBorderY(i, size); + } + field.setTreasurePos(-1, -1); + setOnTouchListener(touchListener); + } + + public EditFieldView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public EditFieldView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + boolean touchedCell(float x, float y) { + int dx = (int) x % CELL_SIZE; + int dy = (int) y % CELL_SIZE; + return (dx > PRECISION && dx < CELL_SIZE - PRECISION) && (dy > PRECISION && dy < CELL_SIZE - PRECISION); + } + + boolean touchedVerticalWall(float x, float y) { + int dx = (int) x % CELL_SIZE; + int dy = (int) y % CELL_SIZE; + return (dx < PRECISION || dx > CELL_SIZE - PRECISION) && (dy > PRECISION && dy < CELL_SIZE - PRECISION); + } + + private boolean touchedHorizontalWall(float x, float y) { + int dx = (int) x % CELL_SIZE; + int dy = (int) y % CELL_SIZE; + return (dx > PRECISION && dx < CELL_SIZE - PRECISION) && (dy < PRECISION || dy > CELL_SIZE - PRECISION); + } + + + void chooseState(int x, int y) { + final int fx = x; + final int fy = y; + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getContext()); + + LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE ); + + View dialogView = inflater.inflate(R.layout.dialog_choosecell, null); + dialogBuilder.setView(dialogView); + + ImageButton bullButton = dialogView.findViewById(R.id.bullButton); + ImageButton nothingButton = dialogView.findViewById(R.id.nothingButton); + ImageButton hospitalButton = dialogView.findViewById(R.id.hospitalButton); + ImageButton treasureButton = dialogView.findViewById(R.id.treasureButton); + + final AlertDialog alertDialog = dialogBuilder.create(); + + bullButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + field.setState(fx, fy, State.MINOTAUR); + invalidate(); + alertDialog.dismiss(); + } + }); + + nothingButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + field.setState(fx, fy, State.NOTHING); + invalidate(); + alertDialog.dismiss(); + } + }); + + hospitalButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + for (int i = 0; i < field.getSize(); i++) + for (int j = 0; j < field.getSize(); j++) { + if (field.getState(i, j) == State.HOSPITAL) + field.setState(i, j, State.NOTHING); + } + field.setHospitalPos(fx, fy); + invalidate(); + alertDialog.dismiss(); + } + }); + + treasureButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + field.setTreasurePos(fx, fy); + updateTreasure(fx, fy); + invalidate(); + alertDialog.dismiss(); + } + }); + alertDialog.show(); + + } + + void processClick(float x, float y) { + if (touchedCell(x, y)) { + int fx = (int) x / CELL_SIZE - offsetX; + int fy = (int) y / CELL_SIZE - offsetY; + if ( fx < 0 + || fx >= field.getSize() + || fy < 0 + || fy >= field.getSize()) + return; + chooseState(fx, fy); + } + else if (touchedVerticalWall(x, y)){ + int wx = ((int) x + PRECISION + 1) / CELL_SIZE - offsetX; + int wy = ((int) y + PRECISION + 1) / CELL_SIZE - offsetY; + if ( wx < 0 + || wx > field.getSize() + || wy < 0 + || wy >= field.getSize()) + return; + field.setBorderY(wy, wx, !field.hasBorderY(wy, wx)); + } + else if (touchedHorizontalWall(x, y)){ + int wx = ((int) x + PRECISION + 1) / CELL_SIZE - offsetX; + int wy = ((int) y + PRECISION + 1) / CELL_SIZE - offsetY; + if ( wx < 0 + || wx >= field.getSize() + || wy < 0 + || wy > field.getSize()) + return; + field.setBorderX(wy, wx, !field.hasBorderX(wy, wx)); + } + + } + + OnTouchListener touchListener = new OnTouchListener() { + float startX = 0, startY = 0; + @Override + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + startX = event.getX(); + startY = event.getY(); + break; + } + case MotionEvent.ACTION_UP: { + if ((startX - event.getX()) < 5 && + (startY - event.getY()) < 5) { + processClick(event.getX(), event.getY() + scrolledY); + invalidate(); + } + break; + } + } + return true; + } + }; + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + } + + public Field getField() { + return field; + } + + public void setField(Field field) { + this.field = field; + invalidate(); + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/FieldView.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/FieldView.java similarity index 59% rename from Labyrinth/app/src/main/java/ru/spbau/labyrinth/FieldView.java rename to Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/FieldView.java index bb30a45..bd368ad 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/FieldView.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/FieldView.java @@ -1,4 +1,4 @@ -package ru.spbau.labyrinth; +package ru.spbau.labyrinth.customviews; import android.content.Context; import android.graphics.Bitmap; @@ -8,72 +8,58 @@ import android.graphics.Paint; import android.support.annotation.Nullable; import android.util.AttributeSet; -import android.view.MotionEvent; import android.view.View; -import ru.spbau.labyrinth.model.Model; -import ru.spbau.labyrinth.model.Model.Direction; -import ru.spbau.labyrinth.model.Model.Player; +import ru.spbau.labyrinth.R; +import ru.spbau.labyrinth.model.field.Field; public class FieldView extends View { private static final int FIELD_WIDTH = 2000; private static final int FIELD_HEIGHT = 2000; - private static final int CELL_SIZE = 200; - private static final int MAZE_OFFSET_X = 3; - private static final int MAZE_OFFSET_Y = 3; + protected static final int CELL_SIZE = 200; + protected int offsetX; + protected int offsetY; private Paint paint; + Bitmap minotaurBmp; + Bitmap hospitalBmp; + private int treasureX = -1; + private int treasureY = -1; + public float scrolledY = 0; - Player myPlayer; + protected Field field; public void scrollY (float y) { scrolledY = y; } - OnTouchListener touchListener = new OnTouchListener() { - float startX = 0, startY = 0; - @Override - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - startX = event.getX(); - startY = event.getY(); - break; - } - case MotionEvent.ACTION_UP: { - if ((startX - event.getX()) < 5 && - (startY - event.getY()) < 5) { - //processClick(event.getX(), event.getY() + scrolledY); - invalidate(); - } - break; - } - } - return true; - } - }; - - /*private void initGame() { - model.demoInit(); - myPlayer = model.processTurn(Direction.NONE, Direction.NONE); - }*/ - - { + private void init() { paint = new Paint(); - setOnTouchListener(touchListener); - //initGame(); + minotaurBmp = Bitmap.createScaledBitmap( + BitmapFactory.decodeResource(getResources(), R.drawable.bull), + CELL_SIZE, + CELL_SIZE, + true); + hospitalBmp = Bitmap.createScaledBitmap( + BitmapFactory.decodeResource(getResources(), R.drawable.hospital), + CELL_SIZE, + CELL_SIZE, + true); } public FieldView(Context context) { super(context); + init(); } public FieldView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); + init(); } public FieldView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(); } @Override @@ -84,12 +70,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { private void drawCells(Canvas canvas) { paint.setStyle(Paint.Style.FILL); - int fieldSize = 5; + int fieldSize = field.getSize(); for (int i = 0; i < fieldSize; i++) for (int j = 0; j < fieldSize; j++) { - int cellX = j + MAZE_OFFSET_X; - int cellY = i + MAZE_OFFSET_Y; - switch (myPlayer.getFieldState(j, i)) { + int cellX = j + offsetX; + int cellY = i + offsetY; + switch (field.getState(j, i)) { case NOTHING: { paint.setColor(Color.WHITE); @@ -111,10 +97,11 @@ private void drawCells(Canvas canvas) { break; } case MINOTAUR: { - Bitmap bitmapSource = BitmapFactory.decodeResource(getResources(), R.drawable.bull); - Bitmap.createScaledBitmap(bitmapSource, CELL_SIZE, CELL_SIZE, true); - canvas.drawBitmap(Bitmap.createScaledBitmap(bitmapSource, CELL_SIZE, CELL_SIZE, true), - CELL_SIZE * cellX, CELL_SIZE * cellY, paint); + canvas.drawBitmap(minotaurBmp, CELL_SIZE * cellX, CELL_SIZE * cellY, paint); + break; + } + case HOSPITAL: { + canvas.drawBitmap(hospitalBmp, CELL_SIZE * cellX, CELL_SIZE * cellY, paint); break; } } @@ -127,14 +114,14 @@ private void drawWalls(Canvas canvas) { paint.setColor(Color.BLACK); paint.setStrokeWidth(6); - int fieldSize = 5; + int fieldSize = field.getSize(); for (int i = 0; i < fieldSize + 1; i++) { for (int j = 0; j < fieldSize; j++) { - int cellX = j + MAZE_OFFSET_X; - int cellY = i + MAZE_OFFSET_Y; - if (myPlayer.getFieldBorderX(i, j)) { + int cellX = j + offsetX; + int cellY = i + offsetY; + if (field.hasBorderX(i, j)) { canvas.drawLine(CELL_SIZE * cellX, CELL_SIZE * cellY, CELL_SIZE * (cellX + 1), @@ -147,9 +134,9 @@ private void drawWalls(Canvas canvas) { for (int i = 0; i < fieldSize; i++) { for (int j = 0; j < fieldSize + 1; j++) { - int cellX = j + MAZE_OFFSET_X; - int cellY = i + MAZE_OFFSET_Y; - if (myPlayer.getFieldBorderY(i, j)) { + int cellX = j + offsetX; + int cellY = i + offsetY; + if (field.hasBorderY(i, j)) { canvas.drawLine(CELL_SIZE * cellX, CELL_SIZE * cellY, CELL_SIZE * cellX, @@ -167,16 +154,6 @@ private void drawBackground(Canvas canvas) { canvas.drawRect(0, 0, getWidth(), getHeight(), paint); } - private void drawPlayer(Canvas canvas) { - paint.setStyle(Paint.Style.FILL); - paint.setColor(Color.RED); - int playerX = myPlayer.getX(); - int playerY = myPlayer.getY(); - canvas.drawCircle(CELL_SIZE * (MAZE_OFFSET_X + playerX) + CELL_SIZE / 2, - CELL_SIZE * (MAZE_OFFSET_Y + playerY) + CELL_SIZE / 2, - CELL_SIZE/3, paint); - } - private void drawGrid(Canvas canvas) { paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(3); @@ -191,9 +168,29 @@ private void drawGrid(Canvas canvas) { paint.setStyle(Paint.Style.FILL); paint.setColor(Color.RED); - /*for (int i = 0; i < points.size(); i++) { - canvas.drawCircle(points.get(i).x, points.get(i).y, 10, paint); - }*/ + } + + private void drawTreasure(Canvas canvas) { + if (treasureX == -1) + return; + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(6); + paint.setColor(Color.BLACK); + + float tx = treasureX + offsetX; + float ty = treasureY + offsetY; + canvas.drawLine( + (tx + (float)0.75) * CELL_SIZE, + (ty + (float)0.1) * CELL_SIZE, + (tx + (float)0.95) * CELL_SIZE, + (ty + (float)0.1) * CELL_SIZE, + paint); + canvas.drawLine( + (tx + (float)0.85) * CELL_SIZE, + (ty + (float)0.1) * CELL_SIZE, + (tx + (float)0.85) * CELL_SIZE, + (ty + (float)0.3) * CELL_SIZE, + paint); } @Override @@ -203,12 +200,11 @@ protected void onDraw(Canvas canvas) { drawCells(canvas); drawGrid(canvas); drawWalls(canvas); - drawPlayer(canvas); + drawTreasure(canvas); } - public void updatePlayer(Player newPlayer) { - myPlayer = newPlayer; - invalidate(); + protected void updateTreasure(int tx, int ty) { + treasureX = tx; + treasureY = ty; } - } diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/OuterScrollView.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/OuterScrollView.java similarity index 72% rename from Labyrinth/app/src/main/java/ru/spbau/labyrinth/OuterScrollView.java rename to Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/OuterScrollView.java index 782daa1..365a455 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/OuterScrollView.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/OuterScrollView.java @@ -1,4 +1,4 @@ -package ru.spbau.labyrinth; +package ru.spbau.labyrinth.customviews; import android.content.Context; import android.util.AttributeSet; @@ -6,25 +6,39 @@ import android.widget.HorizontalScrollView; import android.widget.ScrollView; +import ru.spbau.labyrinth.R; + public class OuterScrollView extends ScrollView { public HorizontalScrollView horizontalScrollView; + public void init() { + post(new Runnable() { + @Override + public void run() { + horizontalScrollView = (HorizontalScrollView) getChildAt(0); + } + }); + } + public OuterScrollView(Context context) { super(context); + init(); } public OuterScrollView(Context context, AttributeSet attrs) { super(context, attrs); + init(); } public OuterScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + init(); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { super.onInterceptTouchEvent(ev); - FieldView fieldView = (FieldView) findViewById(R.id.fieldView); + FieldView fieldView = findViewById(R.id.fieldView); fieldView.scrollY(this.getScrollY()); horizontalScrollView.onInterceptTouchEvent(ev); return true; diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/PlayerFieldView.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/PlayerFieldView.java new file mode 100644 index 0000000..1265b3f --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/customviews/PlayerFieldView.java @@ -0,0 +1,259 @@ +package ru.spbau.labyrinth.customviews; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.support.annotation.Nullable; +import android.util.AttributeSet; + +import ru.spbau.labyrinth.model.Model; +import ru.spbau.labyrinth.model.Model.Direction; +import ru.spbau.labyrinth.model.Model.Player; + +public class PlayerFieldView extends FieldView { + + protected static final int MAZE_OFFSET_X = 5; + protected static final int MAZE_OFFSET_Y = 5; + private Paint paint; + private Direction moveDir = Direction.NONE; + private Direction shootDir = Direction.NONE; + + private final static int playerColors[] = new int[]{Color.RED, + Color.BLUE, + Color.GREEN, + Color.YELLOW}; + + protected Player myPlayer; + + private void init() { + paint = new Paint(); + } + + public PlayerFieldView(Context context) { + super(context); + init(); + } + + public PlayerFieldView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public PlayerFieldView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + private void drawPlayer(Canvas canvas) { + paint.setStyle(Paint.Style.FILL); + + paint.setColor(playerColors[myPlayer.getId()]); + + int playerX = myPlayer.getX(); + int playerY = myPlayer.getY(); + canvas.drawCircle(CELL_SIZE * (offsetX + playerX) + CELL_SIZE / 2, + CELL_SIZE * (offsetY + playerY) + CELL_SIZE / 2, + CELL_SIZE/3, paint); + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + drawMove(canvas); + drawShoot(canvas); + drawPlayer(canvas); + } + + private void drawShoot(Canvas canvas) { + float startX, startY, endX, endY; + paint.setStyle(Paint.Style.STROKE); + paint.setColor(Color.BLACK); + paint.setStrokeWidth(7); + switch (shootDir) { + case LEFT: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX - CELL_SIZE; + endY = startY; + break; + } + case RIGHT: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX + CELL_SIZE; + endY = startY; + break; + } + case UP: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY - CELL_SIZE; + break; + } + case DOWN: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY + CELL_SIZE; + break; + } + default: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY; + } + } + + int dashes = 8; + for (int i = 0; i < dashes; i++) { + if (i % 2 == 0) { + canvas.drawLine( + startX + (endX - startX) * (float)i / (float)dashes, + startY + (endY - startY) * (float)i / (float)dashes, + startX + (endX - startX) * (float)(i + 1) / (float)dashes, + startY + (endY - startY) * (float)(i + 1) / (float)dashes, paint); + } + } + + float ax, ay, bx, by, cx, cy; + Path path = new Path(); + path.setFillType(Path.FillType.EVEN_ODD); + float d = 10; + switch (shootDir) { + case LEFT: { + ax = endX - CELL_SIZE / d; ay = endY; + bx = endX + CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case RIGHT: { + ax = endX + CELL_SIZE / d; ay = endY; + bx = endX - CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX - CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case UP: { + ax = endX; ay = endY - CELL_SIZE / d; + bx = endX - CELL_SIZE / d; by = endY + CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case DOWN: { + ax = endX; ay = endY + CELL_SIZE / d; + bx = endX - CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY - CELL_SIZE / d; + break; + } + default: { + ax = 0; ay = 0; bx = 0; by = 0; cx = 0; cy = 0; + } + } + path.moveTo(ax, ay); + path.lineTo(bx, by); + path.lineTo(cx, cy); + path.close(); + paint.setStyle(Paint.Style.STROKE); + canvas.drawPath(path, paint); + } + + private void drawMove(Canvas canvas) { + float startX, startY, endX, endY; + paint.setStyle(Paint.Style.STROKE); + paint.setColor(Color.BLACK); + paint.setStrokeWidth(7); + switch (moveDir) { + case LEFT: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX - CELL_SIZE; + endY = startY; + break; + } + case RIGHT: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX + CELL_SIZE; + endY = startY; + break; + } + case UP: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY - CELL_SIZE; + break; + } + case DOWN: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY + CELL_SIZE; + break; + } + default: { + startX = CELL_SIZE * (offsetX + myPlayer.getX()) + CELL_SIZE / 2; + startY = CELL_SIZE * (offsetY + myPlayer.getY()) + CELL_SIZE / 2; + endX = startX; + endY = startY; + } + } + canvas.drawLine(startX, startY, endX, endY, paint); + float ax, ay, bx, by, cx, cy; + Path path = new Path(); + path.setFillType(Path.FillType.EVEN_ODD); + float d = 10; + switch (moveDir) { + case LEFT: { + ax = endX - CELL_SIZE / d; ay = endY; + bx = endX + CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case RIGHT: { + ax = endX + CELL_SIZE / d; ay = endY; + bx = endX - CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX - CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case UP: { + ax = endX; ay = endY - CELL_SIZE / d; + bx = endX - CELL_SIZE / d; by = endY + CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY + CELL_SIZE / d; + break; + } + case DOWN: { + ax = endX; ay = endY + CELL_SIZE / d; + bx = endX - CELL_SIZE / d; by = endY - CELL_SIZE / d; + cx = endX + CELL_SIZE / d; cy = endY - CELL_SIZE / d; + break; + } + default: { + ax = 0; ay = 0; bx = 0; by = 0; cx = 0; cy = 0; + } + } + path.moveTo(ax, ay); + path.lineTo(bx, by); + path.lineTo(cx, cy); + path.close(); + paint.setStyle(Paint.Style.FILL_AND_STROKE); + canvas.drawPath(path, paint); + + } + + public void updatePlayer(Player newPlayer, Model.Direction moveDir, Model.Direction shootDir) { + myPlayer = newPlayer; + field = myPlayer.getField(); + updateTreasure(field.getTreasureX(), field.getTreasureY()); + this.moveDir = moveDir; + this.shootDir = shootDir; + offsetX = MAZE_OFFSET_X - myPlayer.getInitialX(); + offsetY = MAZE_OFFSET_Y - myPlayer.getInitialY(); + invalidate(); + } + +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/db/DBHelper.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/db/DBHelper.java new file mode 100644 index 0000000..0fe61f6 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/db/DBHelper.java @@ -0,0 +1,110 @@ +package ru.spbau.labyrinth.db; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import java.util.ArrayList; +import java.util.List; + +import ru.spbau.labyrinth.model.field.Field; + +public class DBHelper extends SQLiteOpenHelper { + public DBHelper(Context context) { + super(context, "Mazes DB", null, 1); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + sqLiteDatabase.execSQL("create table mytable (id integer primary key autoincrement," + + "object text, name text);"); + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) { + + } + + public long saveField(Field field, String name) { + ContentValues contentValues = new ContentValues(); + SQLiteDatabase database = getWritableDatabase(); + + contentValues.put("object", Field.serialize(field)); + contentValues.put("name", name); + long rowID = database.insert( + "mytable", + null, + contentValues); + return rowID; + } + + public List getAllSavedMazesNames() { + SQLiteDatabase db = getWritableDatabase(); + ArrayList mazesNames = new ArrayList<>(); + + Cursor c = db.query("mytable", null, null, null, null, null, null); + + if (c.moveToFirst()) { + while (true) { + String name = c.getString(c.getColumnIndex("name")); + mazesNames.add(name); + if (!c.moveToNext()) { + break; + } + } + } + + close(); + return mazesNames; + } + + public ArrayList getAllSavedMazesIds() { + SQLiteDatabase db = getWritableDatabase(); + + ArrayList ids = new ArrayList<>(); + + Cursor c = db.query("mytable", null, null, null, null, null, null); + + if (c.moveToFirst()) { + while (true) { + int id = c.getInt(c.getColumnIndex("id")); + ids.add(id); + if (!c.moveToNext()) { + break; + } + } + } + + close(); + return ids; + } + + public void deleteMazeById(int id) { + SQLiteDatabase db = getWritableDatabase(); + db.delete("mytable", "id="+Integer.toString(id), null); + } + + + public String findMazeById(int id) { + SQLiteDatabase db = getWritableDatabase(); + + String json = null; + Cursor c = db.query("mytable", null, null, null, null, null, null); + if (c.moveToFirst()) { + while (true) { + int ind = c.getInt(c.getColumnIndex("id")); + if (ind == id) { + json = c.getString(c.getColumnIndex("object")); + } + if (!c.moveToNext()) { + break; + } + } + } + + close(); + return json; + } +} \ No newline at end of file diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/GameState.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/GameState.java new file mode 100644 index 0000000..ac77abe --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/GameState.java @@ -0,0 +1,78 @@ +package ru.spbau.labyrinth.model; + +import android.content.Intent; + +import com.google.gson.Gson; + +import ru.spbau.labyrinth.model.Model.Player; +import ru.spbau.labyrinth.model.Model.Turn; +import ru.spbau.labyrinth.model.field.Field; + +public class GameState { + public Player[] getPlayers() { + return players; + } + + public int getCurrentPlayerNum() { + return currentPlayerNum; + } + + private final Model model; + private Player[] players; + private Turn[] turns; + public final Log log; + private int currentPlayerNum; + public final int playerNum; + + public GameState(String[] names) { + playerNum = names.length; + model = new Model(); + players = model.init(names, 3); + turns = new Model.Turn[playerNum]; + log = new Log(playerNum); + currentPlayerNum = 0; + } + + public GameState(String[] names, Field field) { + playerNum = names.length; + model = new Model(); + players = model.init(names, field); + turns = new Model.Turn[playerNum]; + log = new Log(playerNum); + currentPlayerNum = 0; + } + + public static GameState deserialize(String save) { + return new Gson().fromJson(save, GameState.class); + } + + public int updateTurn(Turn turn) { + turns[currentPlayerNum] = turn; + currentPlayerNum++; + if (currentPlayerNum == playerNum) { + updateRound(turns); + currentPlayerNum = 0; + turns = new Model.Turn[playerNum]; + } + return model.getWinnerId(); + } + + public int updateRound(Turn[] turns) { + log.addRound(turns); + players = model.processTurnMultiplayer(turns); + return model.getWinnerId(); + } + + public String serialize() { + return new Gson().toJson(this); + } + + public String getTreasureOwner() { + int treasureOwner = model.getTreasureOwnerId(); + String owner = "Nobody"; + if (treasureOwner != -1) { + owner = players[treasureOwner].getName(); + } + return owner; + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Log.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Log.java new file mode 100644 index 0000000..d4f3cfd --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Log.java @@ -0,0 +1,56 @@ +package ru.spbau.labyrinth.model; + +import com.google.gson.Gson; + +import java.util.ArrayList; + +import ru.spbau.labyrinth.model.Model.Turn; + +public class Log { + private final int players; + + private ArrayList turns; + + public Log(int players) { + this.players = players; + turns = new ArrayList<>(); + /* + for (int i = 0; i < 5; i++) { + Turn[] turn = new Turn[players]; + for (int j = 0; j < players; j++) { + turn[j] = new Turn(Model.Direction.UP, + Model.Direction.UP, j); + } + turns.add(turn); + } + */ + } + + public void addRound(Turn[] newTurns) { + turns.add(newTurns); + } + + public Turn getTurn(int turn, int player) { + return turns.get(turn)[player]; + } + + public int getTurnsNum() { + return turns.size(); + } + + public static String serialize(Log log) { + Gson gson = new Gson(); + String json = gson.toJson(log); + return json; + } + + public static Log deserialize(String json) { + Gson gson = new Gson(); + Log log = gson.fromJson(json, Log.class); + return log; + } + + public int getPlayerNum() { + return players; + } +} diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Model.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Model.java index 7fe9f4d..29fa9cb 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Model.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/Model.java @@ -1,187 +1,371 @@ package ru.spbau.labyrinth.model; -import ru.spbau.labyrinth.model.field.*; +import com.google.gson.Gson; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +import ru.spbau.labyrinth.model.field.Field; -/** - * class Model. Contains game model. - */ public class Model { private Player[] players; private Field field; - public enum Direction{UP, DOWN, LEFT, RIGHT, NONE}; - - //One player demo. - - private Player demoPlayer; - private Minotaur demoMinotaur; - - public Player demoInit() { - field = new Field(5); - demoPlayer = new Player(2, 2, "Mr. Smith"); - demoMinotaur = new Minotaur(0, 0, "Deadline"); - demoPlayer.setFieldState(0, 0, Field.State.MINOTAUR); - field.setState(0, 0, Field.State.MINOTAUR); - - for (int i = 0; i < 5; i++) { - for (int j = 0; j < 5; j++) { - if (i != 2 || j != 2) - demoPlayer.setFieldState(i, j, Field.State.UNKNOWN); - } - } - - for (int i = 0; i < 5; i++){ - field.addBorderX(0, i); - field.addBorderX(5, i); - field.addBorderY(i, 0); - field.addBorderY(i, 5); - } - field.addBorderX(1, 2); - field.addBorderX(2, 2); - field.addBorderX(2, 1); - field.addBorderX(3, 1); - field.addBorderY(1, 2); - field.addBorderY(2, 2); - field.addBorderY(2, 1); - field.addBorderY(3, 1); - return demoPlayer; + private Set killed = new HashSet<>(); + + public int getWinnerId() { + return winnerId; + } + + private int winnerId = -1; + + public enum Direction {UP, DOWN, LEFT, RIGHT, NONE} + + public int getTreasureOwnerId() { + return field.getTreasureOwnerId(); } - public Player processTurn(Model.Direction moveDir, Model.Direction shootDir) { - int[] d = getPosChange(moveDir); - int newx = demoPlayer.getX() + d[0]; - int newy = demoPlayer.getY() + d[1]; + private void processTurn(Turn turn) { + int index = turn.getId(); + int[] d = getPosChange(turn.getMoveDir()); + int newx = players[index].getX() + d[0]; + int newy = players[index].getY() + d[1]; - if (shootDir != Direction.NONE) { - demoPlayer.spendCartridge(); + if (newx == field.getTreasureX() && newy == field.getTreasureY() && field.getTreasureOwnerId() == -1) { + field.setTreasureOwnerId(index); } - if (newx == demoPlayer.getX() && newy == demoPlayer.getY()) { - return demoPlayer; + if (newx == players[index].getX() && newy == players[index].getY()) { + return; } - if (!isBorder(demoPlayer.getX(), demoPlayer.getY(), newx, newy)) { - demoPlayer.setX(newx); - demoPlayer.setY(newy); - if (demoPlayer.getFieldState(newx, newy) == Field.State.UNKNOWN) { - if (newx == 0 && newy == 0){ - demoPlayer.setFieldState(newx, newy, Field.State.MINOTAUR); - } else { - demoPlayer.setFieldState(newx, newy, Field.State.NOTHING); + if (!field.isBorder(players[index].getX(), players[index].getY(), newx, newy)) { + players[index].setX(newx); + players[index].setY(newy); + if (players[index].getFieldState(newx, newy) == Field.State.UNKNOWN) { + players[index].setFieldState(newx, newy, field.getState(newx, newy)); + } + + if (field.getTreasureOwnerId() == index) { + field.setTreasurePos(newx, newy); + players[index].setTreasurePos(newx, newy); + } + + if (field.getState(newx, newy) == Field.State.MINOTAUR) { + if (field.getTreasureOwnerId() == index) { + field.setTreasureOwnerId(-1); + field.setTreasurePos(newx, newy); + players[index].setTreasurePos(newx, newy); } + players[index].setX(field.getHospitalX()); + players[index].setY(field.getHospitalY()); + players[index].setFieldState(field.getHospitalX(), field.getHospitalY(), Field.State.HOSPITAL); } + + //TODO what if several number of players stepped on the cell with treasure in the same time + if (players[index].getX() == field.getTreasureX() && players[index].getY() == field.getTreasureY() && field.getTreasureOwnerId() == -1) { + field.setTreasureOwnerId(index); + } + + } else { - int ind[] = getBorderInd(demoPlayer.getX(), demoPlayer.getY(), newx, newy); - if (ind[0] == 0) - demoPlayer.setFieldBorderX(ind[1], ind[2]); - else - demoPlayer.setFieldBorderY(ind[1], ind[2]); - } + boolean playerWon = false; + int ind[] = field.getBorderInd(players[index].getX(), players[index].getY(), newx, newy); + if (index == field.getTreasureOwnerId()) { + if ((ind[0] == 0 && field.isExitBorderX(ind[1], ind[2])) + || (ind[0] != 0 && field.isExitBorderY(ind[1], ind[2]))) { + playerWon = true; + winnerId = index; + + } + } - return demoPlayer; + if (!playerWon) { + if (ind[0] == 0) { + if (!field.isExitBorderX(ind[1], ind[2])) { + players[index].setFieldBorderX(ind[1], ind[2]); + } + } else { + if (!field.isExitBorderY(ind[1], ind[2])) { + players[index].setFieldBorderY(ind[1], ind[2]); + } + } + } + } } - private boolean isBorder(int curx, int cury, int newx, int newy){ - int ind[] = getBorderInd(curx, cury, newx, newy); - if (ind[0] == 0) - return field.hasBorderX(ind[1], ind[2]); - else - return field.hasBorderY(ind[1], ind[2]); + /** + * processTurnMultiplayer method is intended for analysis one game turn. + * + * @param turns is array which describes each player turn. + * @return players array, which contains updated information. + */ + public Player[] processTurnMultiplayer(Turn[] turns) { + killed.clear(); + for (Turn turn : turns) { + checkShoots(turn); + } + + for (Integer killedId : killed) { + if (killedId == field.getTreasureOwnerId()) { + field.setTreasurePos(players[killedId].getX(), players[killedId].getY()); + players[killedId].setTreasurePos(players[killedId].getX(), players[killedId].getY()); + field.setTreasureOwnerId(-1); + } + players[killedId].setX(field.getHospitalX()); + players[killedId].setY(field.getHospitalY()); + players[killedId].setFieldState(field.getHospitalX(), field.getHospitalY(), Field.State.HOSPITAL); + } + + for (Turn turn : turns) { + if (!killed.contains(turn.getId())) { + processTurn(turn); + } + } + return players; } - private int[] getBorderInd(int curx, int cury, int newx, int newy) { - if (curx == newx) { - if (cury < newy) { - return new int[] {0, newy, curx}; + private void checkShoots(Turn turn) { + if (turn.getShootDir() == Direction.NONE || !players[turn.getId()].hasCatridge()) { + return; + } + players[turn.getId()].spendCartridge(); + + int curPosX = players[turn.getId()].getX(); + int curPosY = players[turn.getId()].getY(); + + int[] d = getPosChange(turn.getShootDir()); + while (field.cellIsInField(curPosX, curPosY)) { + int newX = curPosX + d[0]; + int newY = curPosY + d[1]; + if (field.cellIsInField(newX, newY) && !field.isBorder(curPosX, curPosY, newX, newY)) { + curPosX = newX; + curPosY = newY; } else { - return new int[] {0, cury, curx}; + break; } - } else { - if (curx < newx) { - return new int[] {1, cury, newx}; - } else { - return new int[] {1, cury, curx}; + + boolean someoneKilled = false; + for (Player player : players) { + if (player.getX() == curPosX && player.getY() == curPosY) { + killed.add(player.getId()); + someoneKilled = true; + break; + } + } + + if (field.getState(curPosX, curPosY) == Field.State.MINOTAUR) { + field.setState(curPosX, curPosY, Field.State.NOTHING); + for (Player player: players) { + player.setFieldState(curPosX, curPosY, Field.State.NOTHING); + } + someoneKilled = true; + } + + if (someoneKilled) { + break; } } } - private int[] getPosChange(Direction direction){ - if (direction == Direction.DOWN){ - return new int[] {0, 1}; - } else if (direction == Direction.UP){ - return new int[] {0, -1}; - } else if (direction == Direction.LEFT){ - return new int[] {-1, 0}; - } else if (direction == Direction.RIGHT){ - return new int[] {1, 0}; - } - return new int[] {0, 0}; + public Player[] init(String[] names, Field newField) { + int n = names.length; + players = new Player[n]; + Random rnd = new Random(); + field = newField; + Set st = new HashSet<>(); + st.add(field.getTreasureX() * n + field.getTreasureY()); + int size = field.getSize(); + int pos[] = generateRandomPosition(rnd); + for (int i = 0; i < n; i++) { + while (st.contains(pos[0] * size + pos[1])) { + pos = generateRandomPosition(rnd); + } + st.add(pos[0] * size + pos[1]); + players[i] = new Player(pos[0], pos[1], names[i], i); + } + for (int i = 0; i < field.getSize(); i++) { + if (!field.hasBorderX(0, i)) { + field.setExitBorderPos(Field.BorderType.HORIZONTAL, 0, i); + field.addBorderX(0, i); + } + if (!field.hasBorderX(size, i)) { + field.setExitBorderPos(Field.BorderType.HORIZONTAL, size, i); + field.addBorderX(size, i); + } + if (!field.hasBorderY(i, 0)) { + field.setExitBorderPos(Field.BorderType.VERTICAL, i, 0); + field.addBorderY(i, 0); + } + if (!field.hasBorderY(i, size)) { + field.setExitBorderPos(Field.BorderType.VERTICAL, i, size); + field.addBorderY(i, size); + } + } + field.setTreasureOwnerId(-1); + return players; } - /** - * Inner class Player. Contains information about player and his filed view. - */ - public class Player{ - private int x, y, hp, cartridgesCnt; - private String name; - private Field fieldView; - /** - * Player constructor. - * @param posx is players x-coordinate. - * @param posy is players y-coordinate. - * @param name is players name. + * game model initial method. + * + * @param names is array of players names. + * @param fieldSize is size of playing field. + * @return array of players, which contains basic information about players. + * Note: id for players will be in the same order as names are given. */ - Player(int posx, int posy, String name){ + public Player[] init(String[] names, int fieldSize) { + int n = names.length; + + Random rnd = new Random(); + + field = generateRandomField(rnd, fieldSize); + players = new Player[n]; + + Set st = new HashSet<>(); + int pos[] = generateRandomPosition(rnd); + for (int i = 0; i < n; i++) { + while (st.contains(pos[0] * fieldSize + pos[1])) { + pos = generateRandomPosition(rnd); + } + + st.add(pos[0] * fieldSize + pos[1]); + + players[i] = new Player(pos[0], pos[1], names[i], i); + } + + while (st.contains(pos[0] * fieldSize + pos[1])) { + pos = generateRandomPosition(rnd); + } + st.add(pos[0] * fieldSize + pos[1]); + field.setState(pos[0], pos[1], Field.State.MINOTAUR); + + while (st.contains(pos[0] * fieldSize + pos[1])) { + pos = generateRandomPosition(rnd); + } + st.add(pos[0] * fieldSize + pos[1]); + field.setState(pos[0], pos[1], Field.State.HOSPITAL); + field.setHospitalPos(pos[0], pos[1]); + + while (st.contains(pos[0] * fieldSize + pos[1])) { + pos = generateRandomPosition(rnd); + } + st.add(pos[0] * fieldSize + pos[1]); + field.setTreasurePos(pos[0], pos[1]); + field.setTreasureOwnerId(-1); + + return players; + } + + // TODO something less genius :) + public Field generateRandomField(Random random, int fieldSize) { + Field f = new Field(fieldSize); + + for (int i = 0; i < fieldSize; i++) { + f.addBorderX(0, i); + f.addBorderX(fieldSize, i); + f.addBorderY(i, 0); + f.addBorderY(i, fieldSize); + } + + int side = Math.abs(random.nextInt()) % 4; + int pos = Math.abs(random.nextInt()) % (fieldSize); + if (side == 0) { + f.setExitBorderPos(Field.BorderType.HORIZONTAL, 0, pos); + } else if (side == 2) { + f.setExitBorderPos(Field.BorderType.HORIZONTAL, fieldSize, pos); + } else if (side == 1) { + f.setExitBorderPos(Field.BorderType.VERTICAL, pos, 0); + } else { + f.setExitBorderPos(Field.BorderType.VERTICAL, pos, fieldSize); + } + + return f; + } + + private int[] generateRandomPosition(Random random) { + int[] pos = new int[2]; + pos[0] = Math.abs(random.nextInt()) % field.getSize(); + pos[1] = Math.abs(random.nextInt()) % field.getSize(); + return pos; + } + + private int[] getPosChange(Direction direction) { + if (direction == Direction.DOWN) { + return new int[]{0, 1}; + } else if (direction == Direction.UP) { + return new int[]{0, -1}; + } else if (direction == Direction.LEFT) { + return new int[]{-1, 0}; + } else if (direction == Direction.RIGHT) { + return new int[]{1, 0}; + } + return new int[]{0, 0}; + } + + //Inner but not nested because players is linked to model + public class Player { + private int x, y, cartridgesCnt, id; + private int initialX, initialY; + private final String name; + private Field fieldView; + + Player(int posx, int posy, String name, int id) { this.x = posx; this.y = posy; + this.initialX = x; + this.initialY = y; this.name = name; + this.id = id; fieldView = new Field(Model.this.field.getSize()); cartridgesCnt = 3; + fieldView.setTreasurePos(-1, -1); + for (int i = 0; i < fieldView.getSize(); i++) { + for (int j = 0; j < fieldView.getSize(); j++) { + fieldView.setState(i, j, Field.State.UNKNOWN); + } + } + + fieldView.setState(x, y, Field.State.NOTHING); } - /** - * getX method, returns players x-coordinate. - * @return players x-coordinate. - */ public int getX() { return x; } - /** - * getY method, returns players x-coordinate. - * @return players y-coordinate. - */ public int getY() { return y; } - public void setX(int x) { + private void setX(int x) { this.x = x; } - public void setY(int y) { + private void setY(int y) { this.y = y; } - public void spendCartridge(){ - if (cartridgesCnt > 0) { - cartridgesCnt--; - } + private boolean hasCatridge() { + return cartridgesCnt > 0; } - public void setFieldState(int x, int y, Field.State state){ + private void spendCartridge() { + cartridgesCnt--; + } + + private void setFieldState(int x, int y, Field.State state) { fieldView.setState(x, y, state); } - public Field.State getFieldState(int x, int y){ + private Field.State getFieldState(int x, int y) { return fieldView.getState(x, y); } - public void setFieldBorderX(int row, int column) { + private void setFieldBorderX(int row, int column) { fieldView.addBorderX(row, column); } - public void setFieldBorderY(int row, int column) { + private void setFieldBorderY(int row, int column) { fieldView.addBorderY(row, column); } @@ -189,41 +373,72 @@ public int getCartridgesCnt() { return cartridgesCnt; } - public boolean getFieldBorderX(int row, int column) { - return fieldView.hasBorderX(row, column); + public int getId() { + return id; } - public boolean getFieldBorderY(int row, int column) { - return fieldView.hasBorderY(row, column); + public int getInitialX() { + return initialX; } - } - public class Minotaur{ - private int x, y; - private String name; + public int getInitialY() { + return initialY; + } - Minotaur(int posx, int posy, String name){ - x = posx; - y = posy; - this.name = name; + public Field getField() { + return fieldView; } - } + private void setTreasurePos(int x, int y) { + fieldView.setTreasurePos(x, y); + } + + public String getName() { + return name; + } + } /** - * findPlayerByPosition method, returns player whose position is (x, y) or null if such player - * doesn't exist. - * @param x is x-coordinate of field on which we are looking for a Player. - * @param y is y-coordinate of field on which we are looking for a Player. - * @return player located by given coordinates or null if such player doesn't exist. + * Turn class is intended for storing information about game turn of one player. */ - /* - public Player findPlayerByPosition(int x, int y){ - for(Player player: players) { - if (player.x == x && player.y == y){ - return player; - } + public static class Turn { + private Model.Direction moveDir; + private Model.Direction shootDir; + private int id; + + /** + * Turn class constructor. + * + * @param moveDirection is enum description of players shot. + * @param shootDirection is enum description of players move. + * @param id is id of this player. + */ + public Turn(Model.Direction moveDirection, Model.Direction shootDirection, int id) { + this.moveDir = moveDirection; + this.shootDir = shootDirection; + this.id = id; + } + + public int getId() { + return id; + } + + public Model.Direction getMoveDir() { + return moveDir; } - return null; - }*/ + + public Model.Direction getShootDir() { + return shootDir; + } + + public static String serialize(Turn turn) { + Gson gson = new Gson(); + return gson.toJson(turn); + } + + public static Turn deserialize(String json) { + Gson gson = new Gson(); + return gson.fromJson(json, Turn.class); + } + } } diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/field/Field.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/field/Field.java index 6ce1903..7069bf1 100644 --- a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/field/Field.java +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/model/field/Field.java @@ -1,28 +1,60 @@ package ru.spbau.labyrinth.model.field; +import com.google.gson.Gson; + import java.util.Arrays; +import java.util.LinkedList; +import java.util.Queue; -import ru.spbau.labyrinth.model.Model.Player; /** * class Field. Contains information about playing field. Note: field is square. */ public class Field { - public enum State{UNKNOWN, NOTHING, MINOTAUR}; // nothing, hospital etc. - private int size; - private State[][] field; - private boolean[][] borderX; - private boolean[][] borderY; + + public enum State {UNKNOWN, NOTHING, MINOTAUR, HOSPITAL} + public enum BorderType {HORIZONTAL, VERTICAL} + public enum ErrorType { + NO_ERROR("Everything is correct"), + MULTIPLE_EXITS("Too many exits!"), + NO_EXITS("No exits!"), + NO_HOSPITAL("No hospital!"), + NOT_LINKED("There are unreachable areas!"), + NO_TREASURE("No treasure!"), + TOO_DENSE("Not enough free space!"); + + private String message; + + ErrorType(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } + private final int size; + private final State[][] field; + private final boolean[][] borderX; //an array of horizontal walls, from topmost to downmost + private final boolean[][] borderY; //an array of vertical walls, from leftmost to rightmost + private final Point treasure; + private final Point hospital; + private final Point exitBorder; + private int treasureOwnerId; + private BorderType exitBorderType; /** * Field constructor, creates new empty field. - * @param fieledSize is size of field. + * + * @param fieldSize is size of field. */ - public Field(int fieledSize) { - size = fieledSize; - field = new State[size][fieledSize]; - borderX = new boolean[fieledSize + 1][fieledSize]; - borderY = new boolean[fieledSize][fieledSize + 1]; - + public Field(int fieldSize) { + size = fieldSize; + field = new State[size][size]; + borderX = new boolean[size + 1][size]; + borderY = new boolean[size][size + 1]; + treasure = new Point(); + hospital = new Point(); + exitBorder = new Point(); for (int i = 0; i < size; i++) { Arrays.fill(field[i], State.NOTHING); } @@ -30,9 +62,10 @@ public Field(int fieledSize) { /** * getSize method returns field size. + * * @return size of the field. */ - public int getSize(){ + public int getSize() { return size; } @@ -40,16 +73,24 @@ public void setState(int x, int y, State state) { field[x][y] = state; } - public State getState(int x, int y){ + public State getState(int x, int y) { return field[x][y]; } - public void addBorderX(int row, int column){ - borderX[row][column] = true; + public void addBorderX(int row, int column) { + setBorderX(row, column, true); } - public void addBorderY(int row, int column){ - borderY[row][column] = true; + public void addBorderY(int row, int column) { + setBorderY(row, column, true); + } + + public void setBorderX(int row, int column, boolean value){ + borderX[row][column] = value; + } + + public void setBorderY(int row, int column, boolean value){ + borderY[row][column] = value; } public boolean hasBorderX(int row, int column) { @@ -60,10 +101,181 @@ public boolean hasBorderY(int row, int column) { return borderY[row][column]; } - /** - * method addObject, adds information about generated Object, which is added to the field. - */ - public void addObject(){ - /* TODO */ + public void setTreasurePos(int row, int column) { + treasure.x = row; + treasure.y = column; + } + + public int getTreasureOwnerId() { + return treasureOwnerId; + } + + public void setTreasureOwnerId(int ownerId) { + treasureOwnerId = ownerId; + } + + public int getTreasureX() { + return treasure.x; + } + + public int getTreasureY() { + return treasure.y; + } + + public int getHospitalX() { + return hospital.x; + } + + public int getHospitalY() { + return hospital.y; + } + + public void setHospitalPos(int row, int column) { + setState(row, column, State.HOSPITAL); + hospital.x = row; + hospital.y = column; + } + + public boolean cellIsInField(int row, int column) { + return row >= 0 && column >= 0 && row < size && column < size; + } + + public void setExitBorderPos(BorderType type, int row, int column) { + exitBorderType = type; + exitBorder.x = row; + exitBorder.y = column; + } + + public boolean isExitBorderX(int row, int column) { + return (exitBorderType == BorderType.HORIZONTAL) && (row == exitBorder.x) && (column == exitBorder.y); + } + + public boolean isExitBorderY(int row, int column) { + return (exitBorderType == BorderType.VERTICAL) && (row == exitBorder.x) && (column == exitBorder.y); + } + + public static String serialize(Field field) { + return new Gson().toJson(field); + } + + public static Field deserialize(String json) { + return new Gson().fromJson(json, Field.class); + } + + private class Point { + int x; + int y; + public Point() { + x = 0; + y = 0; + } + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + + public boolean isBorder(int curx, int cury, int newx, int newy) { + int ind[] = getBorderInd(curx, cury, newx, newy); + if (ind[0] == 0) { + return hasBorderX(ind[1], ind[2]); + } else { + return hasBorderY(ind[1], ind[2]); + } + } + + public int[] getBorderInd(int curx, int cury, int newx, int newy) { + if (curx == newx) { + if (cury < newy) { + return new int[]{0, newy, curx}; + } else { + return new int[]{0, cury, curx}; + } + } else { + if (curx < newx) { + return new int[]{1, cury, newx}; + } else { + return new int[]{1, cury, curx}; + } + } + } + + private boolean isLinked() { + Point start = new Point(); + Queue queue = new LinkedList<>(); + queue.add(start); + int[][] distance = new int[size][size]; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + distance[i][j] = -1; + } + } + distance[0][0] = 0; + int[] dx = new int[]{0, 1, 0, -1}; + int[] dy = new int[]{1, 0, -1, 0}; + while (!queue.isEmpty()) { + Point point = queue.poll(); + for (int i = 0; i < 4; i++) { + Point newPoint = new Point(point.x + dx[i], point.y + dy[i]); + if ( newPoint.x >= 0 + && newPoint.x < size + && newPoint.y >= 0 + && newPoint.y < size + && !isBorder(point.x, point.y, newPoint.x, newPoint.y) + && distance[newPoint.x][newPoint.y] == -1) { + distance[newPoint.x][newPoint.y] = distance[point.x][point.y] + 1; + queue.add(newPoint); + } + } + } + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + if (distance[i][j] == -1) + return false; + } + } + return true; + } + + public ErrorType isCorrect() { + int exitBorderNum = 0; + for (int i = 0; i < size; i++) { + if (!hasBorderX(0, i)) + exitBorderNum++; + if (!hasBorderX(size, i)) + exitBorderNum++; + if (!hasBorderY(i, 0)) + exitBorderNum++; + if (!hasBorderY(i, size)) + exitBorderNum++; + } + if (exitBorderNum < 1) + return ErrorType.NO_EXITS; + if (exitBorderNum > 1) + return ErrorType.MULTIPLE_EXITS; + boolean hasHospital = false; + int freeSpaces = 0; + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + if (getState(i, j) == State.HOSPITAL) { + hasHospital = true; + } + if (getState(i, j) == State.NOTHING) { + freeSpaces++; + } + } + } + if (!hasHospital) + return ErrorType.NO_HOSPITAL; + if (getTreasureX() < 0 || getTreasureY() < 0) + return ErrorType.NO_TREASURE; + + if (!isLinked()) + return ErrorType.NOT_LINKED; + if (freeSpaces < size*size / 2) + return ErrorType.TOO_DENSE; + return ErrorType.NO_ERROR; } } diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerActivity.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerActivity.java new file mode 100644 index 0000000..f4f22b1 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerActivity.java @@ -0,0 +1,297 @@ +package ru.spbau.labyrinth.networkMultiplayer; + +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.Button; + +import com.google.android.gms.auth.api.Auth; +import com.google.android.gms.auth.api.signin.GoogleSignIn; +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInClient; +import com.google.android.gms.auth.api.signin.GoogleSignInOptions; +import com.google.android.gms.auth.api.signin.GoogleSignInResult; +import com.google.android.gms.games.Games; +import com.google.android.gms.games.GamesClient; +import com.google.android.gms.games.InvitationsClient; +import com.google.android.gms.games.Player; +import com.google.android.gms.games.TurnBasedMultiplayerClient; +import com.google.android.gms.games.multiplayer.Multiplayer; +import com.google.android.gms.games.multiplayer.realtime.RoomConfig; +import com.google.android.gms.games.multiplayer.turnbased.TurnBasedMatch; +import com.google.android.gms.games.multiplayer.turnbased.TurnBasedMatchConfig; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; + +import java.util.ArrayList; + +import ru.spbau.labyrinth.OnlineGameActivity; +import ru.spbau.labyrinth.R; +import ru.spbau.labyrinth.StartActivity; + +public class MultiplayerActivity extends AppCompatActivity implements View.OnClickListener { + private static final int RC_SIGN_IN = 9001; + private static final int RC_SELECT_PLAYERS = 9010; + private static final int RC_LOOK_AT_MATCHES = 10001; + + private TurnBasedMultiplayerClient turnBasedMultiplayerClient; + private TurnBasedMatch turnBasedMatch; + private InvitationsClient invitationsClient; + + private MultiplayerMatch multiplayerMatch = MultiplayerMatch.getInstance(); + + private void startGame (int playersCount) { + multiplayerMatch.playersCount = playersCount; + Intent intent = new Intent(MultiplayerActivity.this, OnlineGameActivity.class); + startActivity(intent); + } + + private void startSignInIntent() { + GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this); + multiplayerMatch.googleSignInAccount = account; + + if (account != null) { + onConnected(account); + return; + } + + GoogleSignInClient signInClient = GoogleSignIn.getClient(this, + GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN); + multiplayerMatch.googleSignInClient = signInClient; + Intent intent = signInClient.getSignInIntent(); + startActivityForResult(intent, RC_SIGN_IN); + } + + private void onConnected(GoogleSignInAccount googleSignInAccount) { + turnBasedMultiplayerClient = Games.getTurnBasedMultiplayerClient(this, googleSignInAccount); + invitationsClient = Games.getInvitationsClient(this, googleSignInAccount); + + multiplayerMatch.turnBasedMultiplayerClient = turnBasedMultiplayerClient; + multiplayerMatch.invitationsClient = invitationsClient; + + Games.getPlayersClient(this, googleSignInAccount) + .getCurrentPlayer() + .addOnSuccessListener( + new OnSuccessListener() { + @Override + public void onSuccess(Player player) { + multiplayerMatch.playerId = player.getPlayerId(); + } + } + ).addOnFailureListener(createFailureListener("There was a problem getting the player!")); + + GamesClient gamesClient = Games.getGamesClient(this, googleSignInAccount); + gamesClient.getActivationHint() + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Bundle hint) { + if (hint != null) { + TurnBasedMatch match = hint.getParcelable(Multiplayer.EXTRA_TURN_BASED_MATCH); + + if (match != null) { + multiplayerMatch.updateMatch(match); + } + } + } + }); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == RC_SIGN_IN) { + GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data); + if (result.isSuccess()) { + onConnected(result.getSignInAccount()); + findViewById(R.id.signOutButton).setVisibility(View.VISIBLE); + } else { + String message = result.getStatus().getStatusMessage(); + if (message == null || message.isEmpty()) { + message = getString(R.string.signin_other_error); + } + onDisconnected(); + new AlertDialog.Builder(this).setMessage(message) + .setNeutralButton(android.R.string.ok, null).show(); + } + } else if (requestCode == RC_SELECT_PLAYERS) { + if (resultCode != Activity.RESULT_OK) { + return; + } + + int minAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0); + int maxAutoPlayers = data.getIntExtra(Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0); + + Bundle autoMatchCriteria = null; + if (minAutoPlayers > 0) { + autoMatchCriteria = RoomConfig.createAutoMatchCriteria(minAutoPlayers, maxAutoPlayers, 0); + } + TurnBasedMatchConfig turnBasedMatchConfig = TurnBasedMatchConfig.builder() + .addInvitedPlayers(data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS)) + .setAutoMatchCriteria(autoMatchCriteria) + .build(); + + turnBasedMultiplayerClient.createMatch(turnBasedMatchConfig).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(TurnBasedMatch newTurnBasedMatch) { + turnBasedMatch = newTurnBasedMatch; + multiplayerMatch.turnBasedMatch = turnBasedMatch; + startGame(turnBasedMatch.getParticipantIds().size()); + } + }); + + } else if (requestCode == RC_LOOK_AT_MATCHES) { + if (resultCode != Activity.RESULT_OK) { + return; + } + + TurnBasedMatch match = data.getParcelableExtra(Multiplayer.EXTRA_TURN_BASED_MATCH); + if (match != null) { + turnBasedMatch = match; + multiplayerMatch.turnBasedMatch = turnBasedMatch; + int playerCount = match.getParticipantIds().size(); + startGame(playerCount); + } + } + } + + private void onDisconnected() { + multiplayerMatch.turnBasedMultiplayerClient = null; + multiplayerMatch.invitationsClient = null; + } + + private void showWarning(String title, String message) { + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); + alertDialogBuilder.setTitle(title).setMessage(message); + alertDialogBuilder.setCancelable(false).setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + Intent intent = new Intent(MultiplayerActivity.this, StartActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + }); + + alertDialogBuilder.create().show(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_multiplayer); + + Button signInButton = findViewById(R.id.signInButton); + Button signOutButton = findViewById(R.id.signOutButton); + Button startMatchButton = findViewById(R.id.startMatchButton); + Button checkGamesButton = findViewById(R.id.checkGamesButton); + signInButton.setOnClickListener(this); + signOutButton.setOnClickListener(this); + startMatchButton.setOnClickListener(this); + checkGamesButton.setOnClickListener(this); + + startSignInIntent(); + } + + @Override + public void onClick(View view) { + switch (view.getId()) { + case R.id.startMatchButton: + onStartMatchCLicked(); + break; + case R.id.signInButton: + startSignInIntent(); + break; + case R.id.signOutButton: + signOut(); + break; + case R.id.checkGamesButton: + onCheckGamesClicked(); + break; + } + } + + private void signOut() { + GoogleSignInClient signInClient = GoogleSignIn.getClient(this, + GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN); + signInClient.signOut().addOnCompleteListener(this, + new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + findViewById(R.id.signInButton).setVisibility(View.VISIBLE); + findViewById(R.id.signOutButton).setVisibility(View.GONE); + onDisconnected(); + } + }); + } + + private OnFailureListener createFailureListener(final String string) { + return new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + handleException(e, string); + } + }; + } + + private void handleException(Exception exception, String details) { + new android.app.AlertDialog.Builder(this) + .setMessage(exception.getMessage() + "\n" + details) + .setNeutralButton(android.R.string.ok, null) + .show(); + } + + private String getNextParticipantId(String myPlayerId, TurnBasedMatch match) { + String myParticipantId = match.getParticipantId(myPlayerId); + ArrayList participantIds = match.getParticipantIds(); + + int desiredIndex = -1; + for (int i = 0; i < participantIds.size(); i++) { + if (participantIds.get(i).equals(myParticipantId)) { + desiredIndex = i + 1; + } + } + + if (desiredIndex < participantIds.size()) { + return participantIds.get(desiredIndex); + } + + if (match.getAvailableAutoMatchSlots() <= 0) { + return participantIds.get(0); + } else { + return null; + } + } + + private void onCheckGamesClicked() { + if (turnBasedMultiplayerClient == null) { + return; + } + turnBasedMultiplayerClient.getInboxIntent() + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Intent intent) { + startActivityForResult(intent, RC_LOOK_AT_MATCHES); + } + }); + } + + private void onStartMatchCLicked() { + if (turnBasedMultiplayerClient == null) { + return; + } + turnBasedMultiplayerClient.getSelectOpponentsIntent(1, 1, false) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Intent intent) { + startActivityForResult(intent, RC_SELECT_PLAYERS); + } + }); + } + +} \ No newline at end of file diff --git a/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerMatch.java b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerMatch.java new file mode 100644 index 0000000..b62fe27 --- /dev/null +++ b/Labyrinth/app/src/main/java/ru/spbau/labyrinth/networkMultiplayer/MultiplayerMatch.java @@ -0,0 +1,146 @@ +package ru.spbau.labyrinth.networkMultiplayer; + +import android.support.annotation.NonNull; + +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.auth.api.signin.GoogleSignInClient; +import com.google.android.gms.games.AnnotatedData; +import com.google.android.gms.games.InvitationsClient; +import com.google.android.gms.games.TurnBasedMultiplayerClient; +import com.google.android.gms.games.multiplayer.turnbased.TurnBasedMatch; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; + +import java.util.ArrayList; + + +public class MultiplayerMatch { + private static MultiplayerMatch instance; + + public static MultiplayerMatch getInstance() { + if (instance == null) { + instance = new MultiplayerMatch(); + } + return instance; + } + + public int playersCount; + public String playerId; + public GoogleSignInAccount googleSignInAccount; + public GoogleSignInClient googleSignInClient; + public TurnBasedMultiplayerClient turnBasedMultiplayerClient; + public InvitationsClient invitationsClient; + public TurnBasedMatch turnBasedMatch; + public TurnBasedMatch updatedTurnBasedMatch; + + private boolean needToUpdateRound = false; + private boolean sendingData = false; + private boolean receivingData = false; + + public void onInitiateMatch(TurnBasedMatch match) { + if (turnBasedMatch.getData() != null && !sendingData) { + updateMatch(match); + } else { + startMatch(match); + } + } + + public int getPlayersNumber() { + String myParticipantId = turnBasedMatch.getParticipantId(playerId); + ArrayList ids = turnBasedMatch.getParticipantIds(); + for (int i = 0; i < ids.size(); i++) { + if (ids.get(i).equals(myParticipantId)) { + return i; + } + } + return -1; + } + + private String getNextParticipantId() { + String myParticipantId = turnBasedMatch.getParticipantId(playerId); + + ArrayList participantIds = turnBasedMatch.getParticipantIds(); + int desiredIndex = -1; + + for (int i = 0; i < participantIds.size(); i++) { + if (participantIds.get(i).equals(myParticipantId)) { + desiredIndex = i + 1; + } + } + + if (desiredIndex < participantIds.size()) { + return participantIds.get(desiredIndex); + } + + if (turnBasedMatch.getAvailableAutoMatchSlots() <= 0) { + needToUpdateRound = true; + return participantIds.get(0); + } else { + return null; + } + } + + public boolean isPlayersTurn() { + int turnStatus = turnBasedMatch.getTurnStatus(); + return turnStatus == TurnBasedMatch.MATCH_TURN_STATUS_MY_TURN; + } + + private void startMatch(TurnBasedMatch match) { + turnBasedMatch = match; + playersCount = match.getParticipantIds().size(); + } + + //package private + public void updateMatch(TurnBasedMatch match) { + updatedTurnBasedMatch = match; + } + + public void sendData(byte[] data) { + String nextParticipantId = getNextParticipantId(); + if (sendingData) { + return; + } + sendingData = true; + turnBasedMultiplayerClient.takeTurn(turnBasedMatch.getMatchId(), data, nextParticipantId).addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + turnBasedMatch = task.getResult(); + updateMatch(turnBasedMatch); + } + sendingData = false; + } + }); + if (needToUpdateRound) { + update(); + needToUpdateRound = false; + } + } + + public void update() { + if (receivingData) { + return; + } + receivingData = true; + turnBasedMultiplayerClient.loadMatch(turnBasedMatch.getMatchId()) + .addOnSuccessListener(new OnSuccessListener>() { + @Override + public void onSuccess(AnnotatedData turnBasedMatchAnnotatedData) { + receivingData = false; + turnBasedMatch = turnBasedMatchAnnotatedData.get(); + updateMatch(turnBasedMatch); + } + }); + } + + public void finish() { + turnBasedMultiplayerClient.finishMatch(turnBasedMatch.getMatchId()) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(TurnBasedMatch turnBasedMatch) { + updateMatch(turnBasedMatch); + } + }); + } +} diff --git a/Labyrinth/app/src/main/res/drawable/hospital.bmp b/Labyrinth/app/src/main/res/drawable/hospital.bmp new file mode 100644 index 0000000..525ff64 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/hospital.bmp differ diff --git a/Labyrinth/app/src/main/res/drawable/lab_icon.jpg b/Labyrinth/app/src/main/res/drawable/lab_icon.jpg new file mode 100644 index 0000000..9f1d06a Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/lab_icon.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/labyrinth.jpg b/Labyrinth/app/src/main/res/drawable/labyrinth.jpg new file mode 100644 index 0000000..cba929d Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/labyrinth.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/labyrinth_blue.jpg b/Labyrinth/app/src/main/res/drawable/labyrinth_blue.jpg new file mode 100644 index 0000000..f57b370 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/labyrinth_blue.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/labyrinth_green.jpg b/Labyrinth/app/src/main/res/drawable/labyrinth_green.jpg new file mode 100644 index 0000000..a9a2431 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/labyrinth_green.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/labyrinth_red.jpg b/Labyrinth/app/src/main/res/drawable/labyrinth_red.jpg new file mode 100644 index 0000000..e428949 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/labyrinth_red.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/labyrinth_yellow.jpg b/Labyrinth/app/src/main/res/drawable/labyrinth_yellow.jpg new file mode 100644 index 0000000..5728497 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/labyrinth_yellow.jpg differ diff --git a/Labyrinth/app/src/main/res/drawable/nothing.bmp b/Labyrinth/app/src/main/res/drawable/nothing.bmp new file mode 100644 index 0000000..5d59477 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/nothing.bmp differ diff --git a/Labyrinth/app/src/main/res/drawable/treasure.bmp b/Labyrinth/app/src/main/res/drawable/treasure.bmp new file mode 100644 index 0000000..b948081 Binary files /dev/null and b/Labyrinth/app/src/main/res/drawable/treasure.bmp differ diff --git a/Labyrinth/app/src/main/res/layout/activity_main.xml b/Labyrinth/app/src/main/res/layout/activity_editor.xml similarity index 60% rename from Labyrinth/app/src/main/res/layout/activity_main.xml rename to Labyrinth/app/src/main/res/layout/activity_editor.xml index be6cdab..e159904 100644 --- a/Labyrinth/app/src/main/res/layout/activity_main.xml +++ b/Labyrinth/app/src/main/res/layout/activity_editor.xml @@ -5,21 +5,33 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/layout" - tools:context="ru.spbau.labyrinth.MainActivity"> + tools:context="ru.spbau.labyrinth.EditorActivity"> - + + + app:layout_constraintTop_toTopOf="parent"> - - + +