This commit is contained in:
NikolajDanger
2021-11-02 00:05:00 +01:00
parent fe2572f9f2
commit d9e27910cf
12 changed files with 742 additions and 53 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
bin/
obj/
.vscode/

View File

@ -13,3 +13,27 @@
#---------------------------------- Content ---------------------------------# #---------------------------------- 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

BIN
Content/playing cards.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

308
Entities/Card.cs Normal file
View File

@ -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<IGameEntity> 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);
}
}
}

30
Entities/CardPosition.cs Normal file
View File

@ -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<IGameEntity> entities) {
}
}
}

43
Entities/EmptyDeck.cs Normal file
View File

@ -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<IGameEntity> entities) {
SetSelected(mPosition, mHolding);
if (Selected && mReleased) {
SolitaireGame.logic.FlipDeck();
}
}
}
}

15
Entities/IGameEntity.cs Normal file
View File

@ -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<IGameEntity> entities);
void Draw(SpriteBatch spriteBatch, GameTime gameTime);
}
}

View File

@ -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);
}
}
}

35
Graphics/Sprite.cs Normal file
View File

@ -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
);
}
}
}

117
Logic/GameLogic.cs Normal file
View File

@ -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<int> deck;
public List<IGameEntity> Entities;
private Texture2D _SpriteSheet;
public List<CardPosition> DeckHidden = new List<CardPosition>();
public List<CardPosition> DeckRevealed = new List<CardPosition>();
private void Shuffle<T>(List<T> 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<int> GenerateDeck() {
List<int> newDeck = new List<int>();
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<IGameEntity> entities, Texture2D _spriteSheet) {
deck = GenerateDeck();
Entities = entities;
_SpriteSheet = _spriteSheet;
PlayCards();
}
}
}

View File

@ -7,7 +7,7 @@ namespace Solitaire
[STAThread] [STAThread]
static void Main() static void Main()
{ {
using (var game = new Game1()) using (var game = new SolitaireGame())
game.Run(); game.Run();
} }
} }

166
Solitaire.cs Normal file
View File

@ -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<IGameEntity> 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<EventArgs>(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<Texture2D>("playing cards");
entities = new List<IGameEntity>();
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);
}
}
}