diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3afdc66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +obj/ +.vscode/ \ No newline at end of file diff --git a/Content/Content.mgcb b/Content/Content.mgcb index ddc4c36..641f3ac 100644 --- a/Content/Content.mgcb +++ b/Content/Content.mgcb @@ -13,3 +13,27 @@ #---------------------------------- Content ---------------------------------# +#begin playing cards.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:playing cards.png + +#begin playing cards.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:playing cards.png + diff --git a/Content/playing cards.png b/Content/playing cards.png new file mode 100644 index 0000000..92b966a Binary files /dev/null and b/Content/playing cards.png differ diff --git a/Entities/Card.cs b/Entities/Card.cs new file mode 100644 index 0000000..631fe3c --- /dev/null +++ b/Entities/Card.cs @@ -0,0 +1,308 @@ +using System.Linq; +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; +using Solitaire.Graphics; + +namespace Solitaire.Entities { + public class Card: CardPosition { + const int SelectedOffset = 3; + public int DefaultX; + + public Sprite Sprite {get; private set;} + public Sprite BackSprite {get; private set;} + public Sprite FrontSprite {get; private set;} + public int Value; + public bool Hidden {get; private set;} + private bool Selected; + public CardPosition parent = null; + + private double lastClicked; + + public void setSprite() { + if (Hidden) { + Sprite = BackSprite; + } else { + Sprite = FrontSprite; + } + } + + public Card(int value, bool hidden, Vector2 position, Texture2D spriteSheet): base(position) { + Offset = 13; + Value = value; + Hidden = hidden; + if (Hidden) { + Offset = 8; + } + + Position = position; + DefaultX = (int)position.X; + + Stationary = true; + + BackSprite = new Sprite(spriteSheet, 320, 192, 32, 48); + FrontSprite = new Sprite( + spriteSheet, + (value % 11) * 32, + (value / 11) * 48, + 32, + 48 + ); + + setSprite(); + } + + public void NewPosition(Vector2 position) { + Position = position; + DefaultX = (int)position.X; + } + + public void Hide() { + Hidden = true; + setSprite(); + } + + public void UnHide() { + Hidden = false; + setSprite(); + } + + public override void Draw(SpriteBatch spriteBatch, GameTime gameTime) { + Sprite.Draw(spriteBatch, Position); + } + + private bool inRectangle(Vector2 point, Rectangle rectangle) { + return ( + point.X >= rectangle.Left && + point.X <= rectangle.Right && + point.Y > rectangle.Top && + point.Y < rectangle.Bottom + ); + } + + public void SetSelected(Vector2 mPosition, bool mHolding) { + int height = 40; + if (child != null) { + height = Offset; + } else if (Selected) { + height += SelectedOffset; + } + + Rectangle spritePosition = new Rectangle( + (int)Position.X, (int)Position.Y, 32, height + ); + if ((!Hidden || child == null) && !mHolding && !Following && inRectangle(mPosition, spritePosition) && !Selected && (child == null || !((Card)child).Selected) && (Offset > 0 || child == null)) { + Selected = true; + } else if (!inRectangle(mPosition, spritePosition) && Selected) { + Selected = false; + if (child != null) { + ((Card)child).SetSelected(mPosition, mHolding); + if (((Card)child).Selected) { + ((Card)child).SetPosition(mPosition); + } + } + if (parent != null) { + if (parent is Card) { + ((Card)parent).SetSelected(mPosition, mHolding); + if (((Card)parent).Selected) { + ((Card)parent).SetPosition(mPosition); + } + } + } + } + } + + public void SetPosition(Vector2 mPosition) { + int X; + int Y; + + if (Following) { + X = (int)mPosition.X - 16; + Y = (int)mPosition.Y - 16; + } else { + X = (int)parent.Position.X; + Y = (int)parent.Position.Y + parent.Offset; + } + + if (Selected && !Following) { + Y -= SelectedOffset; + } + + Position = new Vector2(X, Y); + + } + + public void SetStationary(bool stationary) { + Stationary = stationary; + if (child != null) { + ((Card)child).SetStationary(stationary); + } + } + + private bool IsCloser(CardPosition other, CardPosition closest) { + return ( + other.Stationary && + other.child == null && + ( + closest == null || + Vector2.Distance(closest.Position, Position) > Vector2.Distance(other.Position, Position) + ) && + ValidPlacement(other) + ); + } + + private bool ValidPlacement(CardPosition other) { + return ( + other == parent || + ( + !(other is Card) && + ( + ( + !other.TopPile && + ( + ( + Deck && other.Deck + ) || + ( + !other.Deck && + Value % 13 == 12 + ) + ) + ) || + ( + other.TopPile && + Value % 13 == 0 + ) + ) + ) || + ( + other is Card && + ( + ( + !other.Deck && + ( + ( + other.TopPile && + Value / 13 == ((Card)other).Value / 13 && + Value - 1 == ((Card)other).Value + ) || + ( + (Value % 13 + 1 == ((Card)other).Value % 13) && + ((Value / 13) % 2 != (((Card)other).Value / 13) % 2) + ) + ) + ) || + ( + Deck && other.Deck && !((Card)other).Hidden + ) + ) + ) + ); + } + + public override void Update(GameTime gameTime, Vector2 mPosition, bool mReleased, bool mHolding, List entities) { + SetSelected(mPosition, mHolding); + + if (parent != null && !Following) { + DrawOrder = parent.DrawOrder + 1; + } else if (!Following) { + DrawOrder = 1; + } + + if (Selected && mReleased && !Following) { + if (!Hidden) { + lastClicked = gameTime.TotalGameTime.TotalSeconds; + Following = true; + SetStationary(false); + Selected = false; + DrawOrder = 1000; + } else { + UnHide(); + if (Deck) { + parent.child = null; + Selected = false; + SolitaireGame.logic.DeckRevealed.Last().child = this; + parent = SolitaireGame.logic.DeckRevealed.Last(); + SolitaireGame.logic.DeckRevealed.Add(this); + SolitaireGame.logic.DeckHidden.Remove(this); + parent.Offset = 0; + } + } + } else if (Following && mReleased) { + CardPosition closest = null; + CardPosition oldPosition = parent; + + parent.child = null; + + foreach (IGameEntity entity in entities) { + if (entity is CardPosition) { + if (IsCloser((CardPosition)entity, closest)) { + closest = (CardPosition)entity; + } + } + } + + if (closest != null && Vector2.Distance(closest.Position, Position) < 50) { + if (ValidPlacement(closest)) { + Following = false; + SetStationary(true); + closest.child = this; + parent = closest; + if (parent is Card && !((Card)parent).Hidden){ + parent.Offset = 13; + } + if (parent.TopPile) { + TopPile = true; + parent.Offset = 0; + } else { + TopPile = false; + } + + if (Deck && !closest.Deck) { + Deck = false; + SolitaireGame.logic.DeckRevealed.Remove(this); + } + + if (Deck) { + parent.Offset = 0; + } + } + } else { + Following = false; + SetStationary(true); + oldPosition.child = this; + parent = oldPosition; + } + + if (gameTime.TotalGameTime.TotalSeconds - lastClicked < 0.3 && parent == oldPosition) { + foreach (IGameEntity entity in SolitaireGame.entities) { + if (entity is CardPosition && ((CardPosition)entity).TopPile && ((CardPosition)entity).child == null) { + if ( + (entity is Card && ((Card)entity).Value % 13 == Value % 13 - 1 && ((Card)entity).Value / 13 == Value / 13) || + (!(entity is Card) && Value % 13 == 0) + ) { + parent.child = null; + ((CardPosition)entity).child = this; + parent = ((CardPosition)entity); + TopPile = true; + parent.Offset = 0; + + if (Deck) { + Deck = false; + SolitaireGame.logic.DeckRevealed.Remove(this); + } + break; + } + } + } + } + } else if (Following && (mPosition.X < 0 || mPosition.X > SolitaireGame.gameWidth || mPosition.Y < 0 || mPosition.Y > SolitaireGame.gameHeight)) { + Following = false; + SetStationary(true); + } + + SetPosition(mPosition); + } + } +} \ No newline at end of file diff --git a/Entities/CardPosition.cs b/Entities/CardPosition.cs new file mode 100644 index 0000000..51d44fd --- /dev/null +++ b/Entities/CardPosition.cs @@ -0,0 +1,30 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; + +namespace Solitaire.Entities { + public class CardPosition: IGameEntity { + const int SelectedOffset = 0; + public int Offset {get; set;} = 0; + + public bool TopPile = false; + public bool Deck = false; + + public Vector2 Position {get; set;} + public CardPosition child = null; + public bool Stationary = true; + + public int DrawOrder {get; set;} + public bool Following {get; set;} = false; + + public CardPosition(Vector2 position) { + Position = position; + } + + public virtual void Draw(SpriteBatch spriteBatch, GameTime gameTime) { + } + + public virtual void Update(GameTime gameTime, Vector2 mPosition, bool mReleased, bool mHolding, List entities) { + } + } +} \ No newline at end of file diff --git a/Entities/EmptyDeck.cs b/Entities/EmptyDeck.cs new file mode 100644 index 0000000..914fb86 --- /dev/null +++ b/Entities/EmptyDeck.cs @@ -0,0 +1,43 @@ +using Microsoft.Xna.Framework; +using System.Collections.Generic; + +namespace Solitaire.Entities { + public class EmptyDeck: CardPosition { + public bool Selected; + + public EmptyDeck(Vector2 position): base(position) { + + } + + private bool inRectangle(Vector2 point, Rectangle rectangle) { + return ( + point.X >= rectangle.Left && + point.X <= rectangle.Right && + point.Y > rectangle.Top && + point.Y < rectangle.Bottom + ); + } + + public void SetSelected(Vector2 mPosition, bool mHolding) { + int height = 48; + + Rectangle spritePosition = new Rectangle( + (int)Position.X, (int)Position.Y, 32, height + ); + if (child == null && inRectangle(mPosition, spritePosition) && !mHolding) { + Selected = true; + } else if ((!inRectangle(mPosition, spritePosition) && Selected) || child != null) { + Selected = false; + } + } + + public override void Update(GameTime gameTime, Vector2 mPosition, bool mReleased, bool mHolding, List entities) { + SetSelected(mPosition, mHolding); + + if (Selected && mReleased) { + SolitaireGame.logic.FlipDeck(); + } + } + + } +} \ No newline at end of file diff --git a/Entities/IGameEntity.cs b/Entities/IGameEntity.cs new file mode 100644 index 0000000..1865987 --- /dev/null +++ b/Entities/IGameEntity.cs @@ -0,0 +1,15 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; + +namespace Solitaire.Entities { + public interface IGameEntity { + int DrawOrder {get;} + bool Following {get;} + Vector2 Position {get;} + int Offset {get; set;} + + void Update(GameTime gameTime, Vector2 mPosition, bool mReleased, bool mHolding, List entities); + void Draw(SpriteBatch spriteBatch, GameTime gameTime); + } +} \ No newline at end of file diff --git a/Game1.cs b/Game1.cs deleted file mode 100644 index 4d971c9..0000000 --- a/Game1.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; - -namespace Solitaire -{ - public class Game1 : Game - { - private GraphicsDeviceManager _graphics; - private SpriteBatch _spriteBatch; - - public Game1() - { - _graphics = new GraphicsDeviceManager(this); - Content.RootDirectory = "Content"; - IsMouseVisible = true; - } - - protected override void Initialize() - { - // TODO: Add your initialization logic here - - base.Initialize(); - } - - protected override void LoadContent() - { - _spriteBatch = new SpriteBatch(GraphicsDevice); - - // TODO: use this.Content to load your game content here - } - - protected override void Update(GameTime gameTime) - { - if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape)) - Exit(); - - // TODO: Add your update logic here - - base.Update(gameTime); - } - - protected override void Draw(GameTime gameTime) - { - GraphicsDevice.Clear(Color.CornflowerBlue); - - // TODO: Add your drawing code here - - base.Draw(gameTime); - } - } -} diff --git a/Graphics/Sprite.cs b/Graphics/Sprite.cs new file mode 100644 index 0000000..fe20b90 --- /dev/null +++ b/Graphics/Sprite.cs @@ -0,0 +1,35 @@ +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework; + +namespace Solitaire.Graphics { + public class Sprite { + public Texture2D Texture {get; private set;} + + public int X { get; set; } + public int Y { get; set; } + + public int Width {get; set; } + public int Height { get; set; } + + public Color TintColor { get; set; } = Color.White; + + public Sprite(Texture2D texture, int x, int y, int width, int height) { + Texture = texture; + X = x; + Y = y; + Width = width; + Height = height; + } + + + public void Draw(SpriteBatch spriteBatch, Vector2 position) { + spriteBatch.Draw( + Texture, + position, + new Rectangle(X, Y, Width, Height), + TintColor + ); + } + + } +} \ No newline at end of file diff --git a/Logic/GameLogic.cs b/Logic/GameLogic.cs new file mode 100644 index 0000000..7d272af --- /dev/null +++ b/Logic/GameLogic.cs @@ -0,0 +1,117 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Collections.Generic; +using Solitaire.Entities; +using System.Linq; + +namespace Solitaire.Logic { + public class GameLogic { + public List deck; + public List Entities; + private Texture2D _SpriteSheet; + + public List DeckHidden = new List(); + public List DeckRevealed = new List(); + + private void Shuffle(List list) { + System.Random random = new System.Random(); + int n = list.Count; + while (n > 1) { + int k = random.Next(n); + n--; + T temp = list[k]; + list[k] = list[n]; + list[n] = temp; + } + } + + private List GenerateDeck() { + List newDeck = new List(); + + int i = 0; + while (i < 52) + { + newDeck.Add(i++); + } + + Shuffle(newDeck); + return newDeck; + } + + private Card DrawCard() { + Card card = new Card( + deck[0], true, new Vector2(0,0), _SpriteSheet + ); + + deck.RemoveAt(0); + + return card; + } + + private void PlayCards() { + Card card; + CardPosition lastCard; + for (int i = 0; i < 7; i++) { + card = null; + lastCard = new CardPosition(new Vector2(14 + (i * 40),60)); + Entities.Add(lastCard); + for (int n = 0; n <= i; n ++) { + card = DrawCard(); + card.parent = lastCard; + lastCard.child = card; + Entities.Add((Card)card); + lastCard = card; + } + card.UnHide(); + } + CardPosition topPile; + for (int i = 0; i < 4; i++) { + topPile = new CardPosition(new Vector2(134 + (i * 40),8)); + topPile.TopPile = true; + Entities.Add(topPile); + } + + CardPosition deckHidden = new EmptyDeck(new Vector2(14, 8)); + deckHidden.Deck = true; + deckHidden.DrawOrder = 100; + DeckHidden.Add(deckHidden); + Entities.Add(deckHidden); + CardPosition deckRevealed = new CardPosition(new Vector2(54, 8)); + deckRevealed.Deck = true; + DeckRevealed.Add(deckRevealed); + + while (deck.Count() > 0) { + card = DrawCard(); + card.parent = DeckHidden.Last(); + card.Offset = 0; + card.Deck = true; + DeckHidden.Last().child = card; + DeckHidden.Add(card); + Entities.Add(card); + } + } + + public void FlipDeck() { + CardPosition card; + while (DeckRevealed.Count() > 1) { + card = DeckRevealed.Last(); + card.child = null; + ((Card)card).parent = DeckHidden.Last(); + DeckHidden.Last().child = card; + DeckRevealed.Remove(card); + DeckHidden.Add(card); + ((Card)card).Hide(); + ((Card)card).SetPosition(new Vector2(0,0)); + card.DrawOrder = ((Card)card).parent.DrawOrder + 1; + } + } + + public GameLogic(List entities, Texture2D _spriteSheet) { + deck = GenerateDeck(); + Entities = entities; + + _SpriteSheet = _spriteSheet; + PlayCards(); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs index be34f3e..e57a8d1 100644 --- a/Program.cs +++ b/Program.cs @@ -7,7 +7,7 @@ namespace Solitaire [STAThread] static void Main() { - using (var game = new Game1()) + using (var game = new SolitaireGame()) game.Run(); } } diff --git a/Solitaire.cs b/Solitaire.cs new file mode 100644 index 0000000..d66bc1b --- /dev/null +++ b/Solitaire.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Solitaire.Entities; +using Solitaire.Logic; + +namespace Solitaire +{ + public class SolitaireGame : Game + { + public const int gameWidth = 300; + public const int gameHeight = 300; + + private GraphicsDeviceManager _graphics; + private SpriteBatch _spriteBatch; + private SpriteBatch _renderTargetBatch; + private RenderTarget2D renderTarget; + private Rectangle renderTargetRectangle; + + private float renderRatio; + + private MouseState mState; + public Vector2 mPosition; + public bool mClicked; + public bool mReleased; + public bool mHolding; + + private Texture2D _spriteSheet; + + public static GameLogic logic; + + + public static List entities; + + private void setRenderRatio() { + float wRatio = Window.ClientBounds.Width / (float)gameWidth; + float hRatio = Window.ClientBounds.Height / (float)gameHeight; + + if (wRatio > hRatio) { + renderRatio = hRatio; + } else { + renderRatio = wRatio; + } + } + + private Rectangle renderRectangle() { + float drawnWidth = gameWidth * renderRatio; + float drawnHeight = gameHeight * renderRatio; + + + Rectangle rect = new Rectangle( + (int)((Window.ClientBounds.Width - drawnWidth)/2), + (int)((Window.ClientBounds.Height - drawnHeight)/2), + (int)drawnWidth, + (int)drawnHeight + ); + + return rect; + } + + public SolitaireGame() + { + _graphics = new GraphicsDeviceManager(this); + Content.RootDirectory = "Content"; + IsMouseVisible = true; + this.Window.AllowUserResizing = true; + this.Window.ClientSizeChanged += new EventHandler(Window_ClientSizeChanged); + + void Window_ClientSizeChanged(object sender, EventArgs e) { + setRenderRatio(); + } + } + + protected override void Initialize() + { + _graphics.PreferredBackBufferWidth = gameWidth * 2; + _graphics.PreferredBackBufferHeight = gameHeight * 2; + _graphics.ApplyChanges(); + + setRenderRatio(); + + base.Initialize(); + } + + protected override void LoadContent() + { + _spriteBatch = new SpriteBatch(GraphicsDevice); + _renderTargetBatch = new SpriteBatch(GraphicsDevice); + renderTarget = new RenderTarget2D( + GraphicsDevice, gameWidth, gameHeight + ); + + _spriteSheet = Content.Load("playing cards"); + + entities = new List(); + logic = new GameLogic(entities, _spriteSheet); + } + + protected override void Update(GameTime gameTime) + { + renderTargetRectangle = renderRectangle(); + + mState = Mouse.GetState(); + mPosition = mState.Position.ToVector2(); + mPosition.X -= renderTargetRectangle.Left; + mPosition.Y -= renderTargetRectangle.Top; + mPosition /= renderRatio; + + if (mReleased) { + mReleased = false; + } + + if (mState.LeftButton == ButtonState.Pressed && !mClicked) { + mClicked = true; + } else if (mState.LeftButton == ButtonState.Released && mClicked) { + mClicked = false; + mReleased = true; + } + + entities.Sort(delegate(IGameEntity a, IGameEntity b) { + return a.DrawOrder.CompareTo(b.DrawOrder); + }); + + mHolding = false; + foreach (IGameEntity entity in entities) { + if (entity is Card && entity.Following) { + mHolding = true; + } + } + + foreach (IGameEntity entity in entities) { + entity.Update(gameTime, mPosition, mReleased, mHolding, entities); + } + + + base.Update(gameTime); + } + + + protected override void Draw(GameTime gameTime) + { + GraphicsDevice.Clear(Color.Black); + + GraphicsDevice.SetRenderTarget(renderTarget); + GraphicsDevice.Clear(Color.Green); + _spriteBatch.Begin(); + foreach (IGameEntity entity in entities) { + entity.Draw(_spriteBatch, gameTime); + } + _spriteBatch.End(); + GraphicsDevice.SetRenderTarget(null); + + _renderTargetBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, DepthStencilState.Default, RasterizerState.CullNone); + _renderTargetBatch.Draw( + renderTarget, + renderTargetRectangle, + Color.White + ); + _renderTargetBatch.End(); + + base.Draw(gameTime); + } + } +}