package com.adamnickle.deck; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.os.Vibrator; import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import com.adamnickle.deck.Game.Card; import com.adamnickle.deck.Game.CardCollection; import com.adamnickle.deck.Game.CardHolder; import com.adamnickle.deck.Game.DeckSettings; import com.adamnickle.deck.Game.GameMessage; import com.adamnickle.deck.Game.GameSaveIO; import com.adamnickle.deck.Interfaces.ConnectionFragment; import com.adamnickle.deck.Interfaces.GameConnection; import com.adamnickle.deck.Interfaces.GameConnectionListener; import com.adamnickle.deck.Interfaces.GameUiListener; import com.mikepenz.actionitembadge.library.ActionItemBadge; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.HashMap; import de.keyboardsurfer.android.widget.crouton.Crouton; import de.keyboardsurfer.android.widget.crouton.Style; public class GameFragment extends Fragment implements GameConnectionListener, GameUiListener { private static final long LONG_PRESS_VIBRATE = 40L; private int mLastOrientation; private CardDisplayLayout mCardDisplay; private GameConnection mGameConnection; private CardHolder mLocalPlayer; private HashMap< String, CardHolder > mCardHolders; private ArrayList< CardHolder > mPlayers; private CardCollection mDeck; private boolean mHasToldToStart; private SlidingFrameLayout mSlidingTableLayout; private final EnumMap< CardDisplayLayout.Side, CardHolder > mSidesCardHolders; private Vibrator mVibrator; public GameFragment() { mCardHolders = new HashMap< String, CardHolder >(); mPlayers = new ArrayList< CardHolder >(); mDeck = new CardCollection(); mHasToldToStart = false; mSidesCardHolders = new EnumMap< CardDisplayLayout.Side, CardHolder >( CardDisplayLayout.Side.class ); } @Override public void onCreate( Bundle savedInstanceState ) { super.onCreate( savedInstanceState ); setRetainInstance( true ); setHasOptionsMenu( true ); } @Override public void onAttach( Activity activity ) { super.onAttach( activity ); mVibrator = (Vibrator) activity.getSystemService( Context.VIBRATOR_SERVICE ); } @Override public void onActivityCreated( @Nullable Bundle savedInstanceState ) { super.onActivityCreated( savedInstanceState ); mSlidingTableLayout = (SlidingFrameLayout) getActivity().findViewById( R.id.table ); } @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedStateInstance ) { if( mCardDisplay == null ) { mCardDisplay = new CardDisplayLayout( getActivity() ) { private MotionEvent mLastLongPress; @Override public void onBackgroundDown( MotionEvent event ) { if( mSlidingTableLayout != null ) { mSlidingTableLayout.collapseFrame(); } } @Override public void onBackgroundDoubleTap( MotionEvent event ) { final String[] backgroundNames = getResources().getStringArray( R.array.backgrounds ); final String currentBackground = PreferenceManager .getDefaultSharedPreferences( getContext().getApplicationContext() ) .getString( DeckSettings.BACKGROUND, DeckSettings.DEFAULT_BACKGROUND_VALUE ); int selectedBackground = 0; for( int i = 0; i < backgroundNames.length; i++ ) { if( backgroundNames[ i ].equals( currentBackground ) ) { selectedBackground = i; break; } } new AlertDialog.Builder( getContext() ) .setTitle( "Pick background" ) .setSingleChoiceItems( R.array.backgrounds, selectedBackground, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialogInterface, int index ) { final String backgroundName = backgroundNames[ index ]; final int backgroundResource = DeckSettings.getBackgroundResourceFromString( getResources(), backgroundName ); PreferenceManager .getDefaultSharedPreferences( getContext().getApplicationContext() ) .edit() .putString( DeckSettings.BACKGROUND, backgroundName ) .apply(); setBackgroundResource( backgroundResource ); dialogInterface.dismiss(); } } ) .show(); } @Override public void onCardSingleTap( MotionEvent event, PlayingCardView playingCardView ) { playingCardView.flip(); } @Override public void onCardScroll( MotionEvent initialDownEvent, MotionEvent event, float deltaX, float deltaY, PlayingCardView playingCardView ) { super.onCardScroll( initialDownEvent, event, deltaX, deltaY, playingCardView ); if( mSlidingTableLayout.isOpen() && playingCardView.getBottom() < ( mSlidingTableLayout.getBottom() - mSlidingTableLayout.getPaddingBottom() ) ) { mGameConnection.sendCard( playingCardView.getOwnerID(), TableFragment.TABLE_ID, playingCardView.getCard(), playingCardView.getOwnerID() ); final MotionEvent up = MotionEvent.obtain( event ); up.setAction( MotionEvent.ACTION_UP ); this.onTouchEvent( up ); } } @Override public void onBackgroundLongPress( MotionEvent event ) { mLastLongPress = event; mVibrator.vibrate( LONG_PRESS_VIBRATE ); } @Override public void onBackgroundUp( MotionEvent event ) { if( mLastLongPress != null && event.getDownTime() == mLastLongPress.getDownTime() ) { final int pointerIndex = event.getActionIndex(); final float x = event.getX( pointerIndex ); final float y = event.getY( pointerIndex ); final float edgeDistance = getResources().getDimensionPixelSize( R.dimen.max_edge_distance ); if( x < edgeDistance ) { pickPlayerForSide( Side.LEFT ); } else if( x > ( this.getWidth() - edgeDistance ) ) { pickPlayerForSide( Side.RIGHT ); } else if( y < edgeDistance ) { pickPlayerForSide( Side.TOP ); } else if( y > this.getHeight() - edgeDistance ) { pickPlayerForSide( Side.BOTTOM ); } mLastLongPress = null; } } }; inflater.inflate( R.layout.card_display, mCardDisplay, true ); final String backgroundName = PreferenceManager .getDefaultSharedPreferences( getActivity() ) .getString( DeckSettings.BACKGROUND, DeckSettings.DEFAULT_BACKGROUND_VALUE ); final int backgroundResource = DeckSettings.getBackgroundResourceFromString( getResources(), backgroundName ); mCardDisplay.setBackgroundResource( backgroundResource ); mCardDisplay.setGameUiListener( this ); if( mLocalPlayer != null ) { mLocalPlayer.setCardHolderListener( mCardDisplay.getCardHolderListener() ); } mLastOrientation = getResources().getConfiguration().orientation; } else { ( (ViewGroup) mCardDisplay.getParent() ).removeView( mCardDisplay ); final int newOrientation = getResources().getConfiguration().orientation; if( newOrientation != mLastOrientation ) { mCardDisplay.onOrientationChange(); mLastOrientation = newOrientation; } } return mCardDisplay; } @Override public void onResume() { super.onResume(); if( !mHasToldToStart && !mGameConnection.isGameStarted() ) { mHasToldToStart = true; mGameConnection.startGame(); } } @Override public void onPause() { super.onPause(); Crouton.clearCroutonsForActivity( getActivity() ); } @Override public void onDestroy() { super.onDestroy(); mCardDisplay.onViewDestroy(); } @Override public void onCreateOptionsMenu( Menu menu, MenuInflater inflater ) { inflater.inflate( R.menu.game, menu ); if( mGameConnection.isServer() ) { inflater.inflate( R.menu.game_server, menu ); ActionItemBadge .update( this, menu.findItem( R.id.actionDealCards ), getResources().getDrawable( R.drawable.card ), ActionItemBadge.BadgeStyle.BLUE, mDeck.getCardCount() ); } } @Override public boolean onOptionsItemSelected( MenuItem item ) { switch( item.getItemId() ) { case R.id.actionDealCards: handleDealCardsClick(); return true; case R.id.actionClearPlayerHands: handleClearPlayerHandsClick(); return true; case R.id.actionDealSingleCard: handleDealSingleCardClick(); return true; case R.id.shuffleCards: mDeck.shuffle(); getActivity().invalidateOptionsMenu(); return true; case R.id.actionLayoutCards: handleLayoutCardsClick(); return true; case R.id.actionSaveGame: handleSaveGameClick(); return true; case R.id.actionOpenGame: handleOpenGameClick(); return true; case R.id.actionSetSidesPlayers: handleSetPlayerSidesClick(); return true; default: return super.onOptionsItemSelected( item ); } } private void handleSetPlayerSidesClick() { final String[] sideNames = { "Left", "Top", "Right", "Bottom" }; DialogHelper .createSelectItemDialog( getActivity(), "Assign players to side to assist passing:", sideNames, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialogInterface, int whichSide ) { switch( whichSide ) { case 0: pickPlayerForSide( CardDisplayLayout.Side.LEFT ); break; case 1: pickPlayerForSide( CardDisplayLayout.Side.TOP ); break; case 2: pickPlayerForSide( CardDisplayLayout.Side.RIGHT ); break; case 3: pickPlayerForSide( CardDisplayLayout.Side.BOTTOM ); break; } } } ) .setPositiveButton( "Cancel", null ) .show(); } private void pickPlayerForSide( final CardDisplayLayout.Side side ) { final String title = "Assign player to " + side.name() + " side:"; DialogHelper .displayCardHolderList( getActivity(), title, mCardHolders.values(), new DialogHelper.CardHolderOnClickListener() { @Override public void onClick( DialogInterface dialog, CardHolder cardHolder ) { setCardHolderToSide( side, cardHolder ); } } ) .setPositiveButton( "Clear Player", new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int whichButton ) { setCardHolderToSide( side, null ); } } ) .setNegativeButton( "Cancel", null ) .show(); } private void setCardHolderToSide( CardDisplayLayout.Side side, @Nullable CardHolder cardHolder ) { switch( side ) { case LEFT: mSidesCardHolders.put( CardDisplayLayout.Side.LEFT, cardHolder ); mCardDisplay.findViewById( R.id.leftBorder ).setVisibility( cardHolder != null ? View.VISIBLE : View.INVISIBLE ); break; case TOP: mSidesCardHolders.put( CardDisplayLayout.Side.TOP, cardHolder ); mCardDisplay.findViewById( R.id.topBorder ).setVisibility( cardHolder != null ? View.VISIBLE : View.INVISIBLE ); break; case RIGHT: mSidesCardHolders.put( CardDisplayLayout.Side.RIGHT, cardHolder ); mCardDisplay.findViewById( R.id.rightBorder ).setVisibility( cardHolder != null ? View.VISIBLE : View.INVISIBLE ); break; case BOTTOM: mSidesCardHolders.put( CardDisplayLayout.Side.BOTTOM, cardHolder ); mCardDisplay.findViewById( R.id.bottomBorder ).setVisibility( cardHolder != null ? View.VISIBLE : View.INVISIBLE ); break; } } private CardHolder[] getDealableCardHolders( boolean includeDrawPiles) { ArrayList< CardHolder > cardHolders = new ArrayList< CardHolder >( mPlayers ); if( includeDrawPiles ) { for( CardHolder cardHolder : mCardHolders.values() ) { if( cardHolder.getID().startsWith( TableFragment.DRAW_PILE_ID_PREFIX ) ) { cardHolders.add( cardHolder ); } } } return cardHolders.toArray( new CardHolder[ cardHolders.size() ] ); } private void handleClearPlayerHandsClick() { for( CardHolder player : mCardHolders.values() ) { mGameConnection.clearCards( mLocalPlayer.getID(), player.getID() ); } mDeck.resetCards(); getActivity().invalidateOptionsMenu(); } private void handleDealCardsClick() { final CardHolder[] players = getDealableCardHolders( false ); if( mDeck.getCardCount() == 0 ) { DialogHelper.showPopup( getActivity(), "No Cards Left", "There are no cards left to deal.", "OK" ); } else if( mDeck.getCardCount() < players.length ) { DialogHelper.showPopup( getActivity(), "Not Enough Cards Left", "There are not enough cards left to evenly deal to players.", "OK" ); } else { final int maxCardsPerPlayer = mDeck.getCardCount() / players.length; final Integer[] cardsDealAmounts = new Integer[ maxCardsPerPlayer ]; for( int i = 1; i <= maxCardsPerPlayer; i++ ) { cardsDealAmounts[ i - 1 ] = i; } DialogHelper.createSelectItemDialog( getActivity(), "Number of cards to deal to each player:", cardsDealAmounts, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialogInterface, int index ) { int cardsPerPlayer = cardsDealAmounts[ index ]; final Card[][] cardsDealt = new Card[ players.length ][ cardsPerPlayer ]; for( int i = 0; i < cardsPerPlayer && mDeck.getCardCount() > 0; i++ ) { for( int j = 0; j < players.length; j++ ) { cardsDealt[ j ][ i ] = mDeck.removeTopCard(); } } for( int i = 0; i < cardsDealt.length; i++ ) { mGameConnection.sendCards( mLocalPlayer.getID(), players[ i ].getID(), cardsDealt[ i ] ); } getActivity().invalidateOptionsMenu(); } } ).show(); } } private void handleDealSingleCardClick() { final CardHolder[] players = getDealableCardHolders( false ); if( mDeck.getCardCount() == 0 ) { DialogHelper.showPopup( getActivity(), "No Cards Left", "There are no cards left to deal.", "OK" ); } else { DialogHelper.displayCardHolderList( getActivity(), "Select player to deal card to:", Arrays.asList( players ), new DialogHelper.CardHolderOnClickListener() { @Override public void onClick( DialogInterface dialog, CardHolder cardHolder ) { mGameConnection.sendCard( GameConnection.MOCK_SERVER_ADDRESS, cardHolder.getID(), mDeck.removeTopCard(), null ); getActivity().invalidateOptionsMenu(); } } ).show(); } } private void handleLayoutCardsClick() { if( mCardDisplay.getChildCount() == 0 ) { DialogHelper.showPopup( getActivity(), "No card to layout", "You do not have any cards to layout.", "Close" ); } else { DialogHelper.createSelectItemDialog( getActivity(), "Select layout:", new String[]{ "By Rank", "By Suit" }, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialogInterface, int i ) { if( i == 0 ) { mCardDisplay.sortCards( mLocalPlayer.getID(), CardCollection.SortingType.SORT_BY_RANK ); } else if( i == 1 ) { mCardDisplay.sortCards( mLocalPlayer.getID(), CardCollection.SortingType.SORT_BY_SUIT ); } mCardDisplay.layoutCards( mLocalPlayer.getID() ); } } ).show(); } } private void handleSaveGameClick() { if( mGameConnection.isServer() ) { DialogHelper.createEditTextDialog( getActivity(), "Enter Deck game save name:", "OK", "Cancel", new DialogHelper.OnEditTextDialogClickListener() { @Override public void onPositiveButtonClick( DialogInterface dialogInterface, String text ) { if( mGameConnection.saveGame( getActivity().getApplicationContext(), text ) ) { DialogHelper.displayNotification( getActivity(), "Game save successful.", Style.CONFIRM ); } else { DialogHelper.displayNotification( getActivity(), "Game save not successful.", Style.ALERT ); } } } ).show(); } } private void handleOpenGameClick() { final RecyclerView gameSaveRecyclerView = GameSaveIO.getGameSaveCards( getActivity() ); if( gameSaveRecyclerView != null ) { final AlertDialog dialog = DialogHelper .createBlankAlertDialog( getActivity(), "Select game save:" ) .setPositiveButton( "Close", null ) .create(); ( (GameSaveIO.GameSaveCardAdapter) gameSaveRecyclerView.getAdapter() ).setGameSaveOnClickListener( new GameSaveIO.GameSaveCardAdapter.GameSaveOnClickListener() { @Override public void onGameSaveClick( File gameSaveFile ) { if( mGameConnection.openGameSave( getActivity(), gameSaveFile ) ) { DialogHelper.displayNotification( getActivity(), "Game open successful.", Style.CONFIRM ); }else { DialogHelper.displayNotification( getActivity(), "Game open not successful.", Style.ALERT ); } dialog.dismiss(); } } ); gameSaveRecyclerView.getAdapter().registerAdapterDataObserver( new RecyclerView.AdapterDataObserver() { @Override public void onChanged() { if( gameSaveRecyclerView.getAdapter().getItemCount() == 0 ) { if( dialog.isShowing() ) { dialog.dismiss(); handleOpenGameClick(); } } } } ); dialog.setView( gameSaveRecyclerView ); dialog.show(); } else { DialogHelper.showPopup( getActivity(), "Select game save:", "There are no game saves to open.", "OK" ); } } /******************************************************************* * GameUiListener Methods *******************************************************************/ @Override public boolean onAttemptSendCard( final String ownerID, final Card card, CardDisplayLayout.Side side ) { if( this.canSendCard( mLocalPlayer.getID(), card ) ) { final CardHolder cardHolder = mSidesCardHolders.get( side ); if( cardHolder != null ) { mGameConnection.sendCard( ownerID, cardHolder.getID(), card, ownerID ); } else { DialogHelper.displayCardHolderList( getActivity(), "Select player to send card to:", mCardHolders.values(), new DialogHelper.CardHolderOnClickListener() { @Override public void onClick( DialogInterface dialogInterface, CardHolder clickedCardHolder ) { if( clickedCardHolder != null ) { mGameConnection.sendCard( ownerID, clickedCardHolder.getID(), card, ownerID ); } else { mCardDisplay.resetCard( mLocalPlayer.getID(), card ); } dialogInterface.dismiss(); } } ).setOnCancelListener( new DialogInterface.OnCancelListener() { @Override public void onCancel( DialogInterface dialogInterface ) { mCardDisplay.resetCard( mLocalPlayer.getID(), card ); dialogInterface.dismiss(); } } ).show(); } return true; } return false; } @Override public boolean canSendCard( String ownerID, Card card ) { CardHolder player = mCardHolders.get( ownerID ); return mCardHolders.size() > 1 && player != null && player.hasCard( card ); } /******************************************************************* * GameConnectionListener Methods *******************************************************************/ @Override public void setGameConnection( GameConnection gameConnection ) { mGameConnection = gameConnection; } @Override public boolean canHandleMessage( GameMessage message ) { return true; } @Override public void onCardHolderConnect( String ID, String name ) { final CardHolder cardHolder = new CardHolder( ID, name ); mCardHolders.put( ID, cardHolder ); if( mGameConnection.isPlayerID( ID ) ) { mPlayers.add( cardHolder ); DialogHelper.displayNotification( getActivity(), name + " joined the game.", Style.CONFIRM ); } } @Override public void onCardHolderNameReceive( String senderID, String newName ) { final CardHolder player = mCardHolders.get( senderID ); player.setName( newName ); DialogHelper.displayNotification( getActivity(), player.getName() + " joined the game.", Style.CONFIRM ); } @Override public void onCardHolderDisconnect( String ID ) { if( mGameConnection.isGameStarted() ) { CardHolder player = mCardHolders.remove( ID ); mPlayers.remove( player ); if( player != null ) { DialogHelper.displayNotification( getActivity(), player.getName() + " left game.", Style.ALERT ); } } } @Override public void onGameStarted() { mLocalPlayer = new CardHolder( mGameConnection.getLocalPlayerID(), "ME" ); if( mCardDisplay != null ) { mLocalPlayer.setCardHolderListener( mCardDisplay.getCardHolderListener() ); } mCardHolders.put( mLocalPlayer.getID(), mLocalPlayer ); mPlayers.add( mLocalPlayer ); } @Override public void onServerConnect( String serverID, String serverName ) { if( !mGameConnection.isServer() ) { DialogHelper.displayNotification( getActivity(), "Connected to " + serverName + "'s server", Style.CONFIRM ); } else { DialogHelper.displayNotification( getActivity(), "Server started", Style.CONFIRM ); } String playerName = PreferenceManager .getDefaultSharedPreferences( getActivity().getApplicationContext() ) .getString( DeckSettings.PLAYER_NAME, mGameConnection.getDefaultLocalPlayerName() ); mGameConnection.sendCardHolderName( mLocalPlayer.getID(), GameConnection.MOCK_SERVER_ADDRESS, playerName ); } @Override public void onServerDisconnect( String serverID ) { Activity activity = getActivity(); if( activity != null ) { activity.setResult( GameActivity.RESULT_DISCONNECTED_FROM_SERVER, new Intent( GameActivity.class.getName() ) ); activity.finish(); } } @Override public void onNotification( String notification, Style style ) { DialogHelper.displayNotification( getActivity(), notification, style ); } @Override public void onConnectionStateChange( ConnectionFragment.State newState ) { final Activity activity = getActivity(); if( activity != null ) { activity.runOnUiThread( new Runnable() { @Override public void run() { activity.invalidateOptionsMenu(); } } ); } } @Override public void onCardReceive( String senderID, String receiverID, Card card ) { if( receiverID.equals( mLocalPlayer.getID() ) ) { mLocalPlayer.addCard( card ); } } @Override public void onCardsReceive( String senderID, String receiverID, Card[] cards ) { if( receiverID.equals( mLocalPlayer.getID() ) ) { mLocalPlayer.addCards( cards ); } } @Override public void onCardRemove( String removerID, String removedID, Card card ) { if( removedID.equals( mLocalPlayer.getID() ) ) { mCardHolders.get( removedID ).removeCard( card ); } } @Override public void onCardsRemove( String removerID, String removedID, Card[] cards ) { if( removedID.equals( mLocalPlayer.getID() ) ) { mCardHolders.get( removedID ).removeCards( cards ); } } @Override public void onClearCards( String commanderID, String commandedID ) { if( commandedID.equals( mLocalPlayer.getID() ) ) { mLocalPlayer.clearCards(); String notification; if( commanderID.equals( mLocalPlayer.getID() ) ) { notification = "You cleared your hand."; } else if( commanderID.equals( GameConnection.MOCK_SERVER_ADDRESS ) ) { notification = "The server host cleared your hand."; } else { notification = mCardHolders.get( commanderID ).getName() + " cleared your hand."; } DialogHelper.displayNotification( getActivity(), notification, Style.INFO ); } } @Override public void onGameOpen( String senderID, String receiverID, Card[] cards ) { if( receiverID.equals( mLocalPlayer.getID() ) ) { mLocalPlayer.clearCards(); mLocalPlayer.addCards( cards ); } } @Override public void onReceiveCardHolders( String senderID, String receiverID, CardHolder[] cardHolders ) { for( CardHolder cardHolder : cardHolders ) { this.onCardHolderConnect( cardHolder.getID(), cardHolder.getName() ); } } }