Welcome, Guest
You have to register before you can post on our site.

Username
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 20,965
» Latest member: Cut
» Forum threads: 22,504
» Forum posts: 23,488

Full Statistics

Online Users
There are currently 1550 online users.
» 0 Member(s) | 1545 Guest(s)
Applebot, Baidu, Bing, Facebook, Google

Latest Threads
[WoW Retail News] New Twi...
Forum: World of Warcraft
Last Post: xSicKxBot

» Replies: 0
» Views: 6
How to get the Ice Staff ...
Forum: PC Discussion
Last Post: xSicKxBot

» Replies: 0
» Views: 6
[WoW Retail News] Preserv...
Forum: World of Warcraft
Last Post: xSicKxBot

» Replies: 0
» Views: 23
How to unlock TR2 in Blac...
Forum: PC Discussion
Last Post: xSicKxBot

» Replies: 0
» Views: 26
[WoW Retail News] New Owl...
Forum: World of Warcraft
Last Post: xSicKxBot

» Replies: 0
» Views: 29
[WoW Retail News] Four Ne...
Forum: World of Warcraft
Last Post: xSicKxBot

» Replies: 0
» Views: 38
Where to Download WoW 3.3...
Forum: World of Warcraft
Last Post: vaan984

» Replies: 3
» Views: 1,329
How to unlock Cypher 091 ...
Forum: PC Discussion
Last Post: xSicKxBot

» Replies: 0
» Views: 41
How to Remux Video Contai...
Forum: Home Servers & Self-Hosting
Last Post: SickProdigy

» Replies: 0
» Views: 38
[WoW Retail News] Win a S...
Forum: World of Warcraft
Last Post: xSicKxBot

» Replies: 0
» Views: 46

 
  News - Blog: Understanding the 4X genre through an RPG lens
Posted by: xSicKxBot - 01-27-2020, 03:11 PM - Forum: Lounge - No Replies

Blog: Understanding the 4X genre through an RPG lens

The following blog post, unless otherwise noted, was written by a member of Gamasutra’s community.
The thoughts and opinions expressed are those of the writer and not Gamasutra or its parent company.


4X games are always classified as strategy games, with good reason, but a lot of their pieces only makes sense when seen through an RPG lens. The genre lets you roleplay leading a nation, or a people, or a race of sentient fungi, through history and this roleplay is a large part of what makes these games feel how they do.

This shouldn’t be a surprise given the rest of the work by the founding father of the genre, Sid Meier. Games like Pirates and Covert Action are about the idea of being a pirate or a spy more than anything else. So much of Civilization doesn’t make sense unless you see it as part of a box of toys meant to enable role play. Things like spies or planes or nukes just do not justify their costs otherwise. Even climate change in Civilization 6 doesn’t do enough to make it worth making unless built from the top-down.

This is just a compelling fantasy to play through. The idea of bringing a nation from birth to victory is empowering.

Additionally, many games now blur the lines, especially the Paradox Grand Strategy games. Games like Crusader Kings live in the fact that the role-playing is critical to the whole experience. It’s clear that the role-playing is an essential part of the whole 4X experience.

Ramifications


This role-playing element opens up a lot for the genre. For a game like Civ, it allows for the rest of the game to be much looser than it would need to be otherwise. I play Civ at a lower difficulty than would really challenge me because I’m not playing it to be challenged as much as I’m playing it for the fun of building out the empire. By reducing this focus on the systemic nature of the decision making, the game becomes a lot easier to make. This is what allows the AI to be bad without the game completely falling apart, what allows the balance issues and what lets the complexity be off the charts without hurting the game severely.

This is to say that the role-playing aspects of the game often come heavily into play when it comes to the turn-to-turn decision making parts of the game. When I choose which district to make for a new city in Civilization, I tend strongly towards Theater Squares and Campuses because I’m attracted to the idea of a nation with high culture and science more than out of a desire to win the game. Similarly, I’m even hesitant about deforestation and I love making natural preserves because they agree with my real world policies. I could easily continue generating tons of carbon dioxide without hurting my chances of winning, but it’s very satisfying to shift to renewable energy simply because I like renewable energy.

In this way, the theming of the game fundamentally affects the decision making of the game, and so the texture of those decisions changes too. The question changes from which build option would be best for me to which build option do I most want. This firstly adds depth as there are always lots of things that I want to build, some of which come from the systems. Additionally, it helps deal with the complexity as when I don’t know what would be best for my nation, I can always default to what I want most for my nation.

A great way to see how this comes into play is to look at the very good Vlaada game Through The Ages. TtA is very much a Eurogame and any role-playing aspects to the game are completely overshadowed by the game’s unforgiving focus on math. Your feelings on the Pyramids versus the Colossus as ancient wonders are immaterial. What matters is entirely what each of those will do to help you win the game.

That’s not to say that those considerations do not come into play in Civilization. Often, I’ll want to build a library and be pushed into building a swordsman instead so as to forestall an invasion. It is however that most 4X games give you the space to determine your own path. The possibility space for victory is so large that there’s a lot of scope for self-expression in how you play them.

Of course, many players care much more for the strategic side of the game than the role-playing side, but the nice thing is that none of this precludes that. You can happily play a 4X game indefinitely free of the theming. However, this role-playing allows the genre a much larger audience than would otherwise be possible.

Missing Fantasies


Seeing the genre from this perspective makes it clear how much space to explore is left in the genre. That the fantasies are all so explicitly nationalistic is as though you could only play one side of an RPG. I’m going to end this article just briefly going over a lot of possible thinking points for what other fantasies can be.

First though, I just want to make a quick, big picture point about the perspective people come to this with. History tends to focus on the big things, the big events and the big people. They tend to be nationalistic in their view and often present winners and losers. So, it’s easy to take the same notions in to building a 4X game. There’s a Gandhi quote from Hind Swaraj that has always stuck with me here, “But if [history] means the doings of kings and emperors, there can be no evidence of soul-force or passive resistance in such history. You cannot expect silver-ore in a tin-mine.”

Here’s a quick list of ideas:

  • Happiness – In the real world, I don’t want a government that focuses on wars or wonders, I want one that makes my daily life a little better. Citizen happiness is not just a way to increase productivity, it’s the explicit goal that I want, not a means to an end.
  • Wakanda – Isolating yourself from the rest of the world in order to build your society and technology.
  • A global leader – Someone who would lead in a time of crisis. Someone who could play peacemaker or solve climate change or even act as the city on the hill, fantasy though it is.
  • Utopia – A post-scarcity world, an ecotopia, a fully-automated luxury communism. A utopia as something to be achieved and not built.
  • Nomadic lifestyle – It’s not necessary to follow the path charted by humans thus far. We can have games with nomads, games that let people feel enough is sufficient, games that are willing to be satisfied.
  • Passing peacefully – Just letting a people pass quietly into the void that has a quiet appeal. I would enjoy playing a 4X with that as the final goal.

– @murthynikhil



https://www.sickgaming.net/blog/2020/01/...-rpg-lens/

Print this item

  News - Pokemon Go Celebrates Lunar New Year With Red-Colored And Rat-Themed Pokemon
Posted by: xSicKxBot - 01-27-2020, 11:47 AM - Forum: Lounge - No Replies

Pokemon Go Celebrates Lunar New Year With Red-Colored And Rat-Themed Pokemon

The Pokemon Go Team has revealed details for a Lunar New Year-themed event for the mobile game. The Pokémon Go Lunar New Year event is live right now and will continue through to Monday, February 3 at 1 PM PT / 4 PM ET.

During the event, certain red-colored Pokemon will appear more often in the wild--be on the lookout for Charmeleon, Vulpix, Parasect, Voltorb, Jynx, Magmar, Magikarp, Flareon, Slugma, Wurmple, Corphish, Kricketot, Foongu, and (perhaps the most famous red Pokemon) Shiny Gyarados.

Generation V's Darumaka makes its debut in Pokemon Go during the Lunar New Year event, and it will hatch from 7km Eggs. The event will also feature several in-game bonuses too, such as random Rare Candy rewards when opening Gifts.

This Lunar New Year marks the start of the Year of the Rat, so you can expect plenty of rat-themed content during a Limited Research event on February 2 from 2 PM PT / 5 PM ET to 5 PM PT / 8 PM ET. During the event, you'll have the chance to encounter Generation V's Minccino--both normal and shiny versions. Rattata, Raticate, Pikachu, Sandshrew, Nidoran♀, Nidoran♂, Sentret, Marill, Zigzagoon, Plusle, Minun, Bidoof, and Patrat will all appear more often in the wild as well.

Niantic--the studio behind Pokemon Go, Harry Potter: Wizards Unite, and Ingress--has announced that 2020 will feature several "immersive interactive experiences" for each game. Pokemon Go will have four events, which are detailed below.

Pokémon Go 2020 Events

  • Taiwan Lantern Festival in Taichung - February 6-9
  • Pokémon GO Safari Zone St. Louis - March 27-29
    • Tickets will go live starting January 24 at 8:00 AM CT
  • Pokémon GO Safari Zone Liverpool - April 17-19
    • Tickets will go on sale starting January 31 at 8:00 AM GMT
  • Pokémon GO Safari Zone Philadelphia - May 8-10
    • More information about tickets will be announced soon

https://www.gamespot.com/articles/pokemo...0-6473064/

Print this item

  AppleInsider - Rumor suggests iOS 14 will run on all iOS 13 capable devices
Posted by: xSicKxBot - 01-27-2020, 04:57 AM - Forum: Apple Mac & macOS - No Replies

Rumor suggests iOS 14 will run on all iOS 13 capable devices

 

The next major version of iOS will be supported on all devices capable of running iOS 13. In contrast, two models of iPad will not support the shift to iPadOS 14, according to a source with a dubious track record.

Apple periodically updates the list of devices that are compatible with iOS, removing older models, and reducing the number of devices the company has to support the software. Last year, Apple phased out the iPhone 5s, iPhone 6, and iPhone 6 Plus, as well as the sixth-generation iPod Touch.

With WWDC on the horizon, we’re expecting to get news of the new iOS 14 and iPadOS 14 during the event, and with that, the possibility of device compatibility changes. France-based iPhonesoft has proposed that iOS 14 and will support all the same devices as iOS 13 does. They also include the iPhone 12, iPhone 12 Pro, and the rumored iPhone SE 2.

Much like iOS 13, the iPhone SE is included in this list, but the iPhone 6 and 6 Plus are not. iPhonesoft does hedge their bets, stating their source suggests the A9 chip should be supported, but Apple may choose to drop support for the iPhone 6 line and the iPhone SE later in development.

As far as iPadOS goes, they suggest the supported devices list will not include all the same iPadOS 13 compatible devices, with Apple dropping support for the iPad mini 4 and the iPad Air 2. They suggest the A8 and A8X chips will not be compatible with the new iPadOS.

These lists should be taken with a grain of salt, as iPhonesoft hasn’t been known for accurately predicting device compatibility. In 2019, they had suggested that the iPhone SE would not support the switch to iOS 13.



https://www.sickgaming.net/blog/2020/01/...e-devices/

Print this item

  News - EB Games Canada Says Street Fighter V: Champion Edition Is Coming To Switch
Posted by: xSicKxBot - 01-27-2020, 04:56 AM - Forum: Nintendo Discussion - No Replies

EB Games Canada Says Street Fighter V: Champion Edition Is Coming To Switch

Street Fighter V

Update 2 – Sun 26th Jan, 2020 09:21 GMT: If you’re still not convinced, Street Fighter’s executive producer Yoshinori Ono also shared the following message, in response to the now-deleted tweet:



Update – Sun 26th Jan, 2020 04:30 GMT: Sorry folks, EB Games Canada is now saying the details in this tweet were inaccurate and has apologised for any disappointment or confusion it may have caused. The intention was to highlight the PS4 version:



Original Story – Sun 26th Jan, 2020 03:00 GMT: Earlier today, the official Twitter account of EB Games Canada announced Street Fighter V: Champion Edition would be coming to the Nintendo Switch. Although the tweet was up for a number of hours, it’s since been deleted.

Here’s a screenshot of it, courtesy of IGN:

Eb Games 720x778

A game like this coming to the Nintendo Switch is rather hard to believe when considering the fact the original was developed in partnership with Sony, and Capcom previously said the original game – or any future versions – would remain exclusive to PlayStation and PC.

Capcom or Nintendo haven’t said anything about porting the game across to the Switch – so it’s either a mistake or EB has made an announcement ahead of schedule. The last Street Fighter Capcom released on Switch was the 30th Anniversary Collection in May 2018. Before then, Ultra Street Fighter II: The Final Challengers arrived in May 2017.

What do you make of this tweet? Tell us below.



https://www.sickgaming.net/blog/2020/01/...to-switch/

Print this item

  News - PlayStation Plus Deal: Grab A 12-Month PS Plus Sub For $37
Posted by: xSicKxBot - 01-27-2020, 04:56 AM - Forum: Lounge - No Replies

PlayStation Plus Deal: Grab A 12-Month PS Plus Sub For $37

With a PlayStation Plus subscription, you get two free PS4 games every month, full online multiplayer functionality, and you can upload your games' save files to the cloud. Typically, purchasing 12 months will set you back $60, but you can get a one-year membership for $37 on Ebay right now. Honestly, if you've been waiting for a discount before buying PS Plus, this deal is probably about as cheap as you're going to find.

A limited quantity is available, so don't wait if you've interested in renewing or extending your PS Plus subscription. The listing is a bit unclear, but according to reviews, you should receive the PS Plus code via email within a day. You can then redeem the code in the PlayStation Store to automatically add another 12 months to your membership.

In the meantime, January's free games are available now for PS Plus subscribers: Uncharted: The Nathan Drake Collection and Goat Simulator. The Nathan Drake Collection contains remastered versions of the first three Uncharted games, which originally released on PS3. Goat Simulator, on the other hand, is a zany sandbox game that lets you wreak havoc as a goat.


https://www.gamespot.com/articles/playst...0-6472484/

Print this item

  Bring joy to development with Quarkus, the cloud-native Java framework
Posted by: xSicKxBot - 01-27-2020, 02:17 AM - Forum: Java & JVM Languages - No Replies

Bring joy to development with Quarkus, the cloud-native Java framework

Our first DevNation Live regional event was held in Bengaluru, India in July. This free technology event focused on open source innovations, with sessions presented by elite Red Hat technologists.

Quarkus is revolutionizing the way that we develop Java applications for the cloud-native era, and in this presentation, Edson Yanaga explains why it also sparks joy.

Watch this live coding session to get familiar with Quarkus and learn how your old and new favorite APIs will start in a matter of milliseconds and consume tiny amounts of memory. Hot reload capabilities for development will bring you instant joy.

Watch the complete presentation:

See the slides here.

Learn more


Join us at an upcoming developer event, and see our collection of past DevNation Live tech talks.

Share

The post Bring joy to development with Quarkus, the cloud-native Java framework appeared first on Red Hat Developer.



https://www.sickgaming.net/blog/2019/10/...framework/

Print this item

  [Oracle Blog] Java Magazine on Containers
Posted by: xSicKxBot - 01-27-2020, 02:17 AM - Forum: Java & JVM Languages - No Replies

Java Magazine on Containers

By Java Magazine Editor Andrew Binstock


In our previous issue, we explored the use of lightweight frameworks— Javalin, Micronaut, and Helidon—to create microservices, which typically are deployed in the cloud. In that issue’s article on Helidon, we also showed how to package a service into a Docker container for deployment.


In this issue, we continue the theme by examining how to build apps with containers in mind and how to deploy containers. For straight Java apps, the jlink and jdeps tools are excellent solutions for creating modularized, small, self-contained apps. We discuss how to use those tools on page 25.


If very fast startup time is a concern, then consider the GraalVM platform. It is written in Java but compiles Java code to an executable format. We’ve discussed GraalVM in past issues, but this article focuses on the latest features and their use in creating small executables with native-code startup speed. Finally, if you’re straddling the Dev and Ops sides of DevOps, you surely have seen that most containers are managed with the open source Kubernetes platform.


In our lead feature, we give you a full introduction to Kubernetes and all the information you need to start working with managing your containerized apps. In addition, we explore what’s new in the recent release of Java 12, and we examine a major upgrade to Java Card, which in all senses is the very smallest container for a Java app.


In addition, we have our usual quiz and our events calendar. Finally, future issues of this magazine will look materially different from what you’re used to. Please see the editorial in this issue for details




https://blogs.oracle.com/java/java-magaz...containers

Print this item

  [Tut] Python Re ? Quantifier
Posted by: xSicKxBot - 01-27-2020, 02:17 AM - Forum: Python - No Replies

Python Re ? Quantifier

Congratulations, you’re about to learn one of the most frequently used regex operators: the question mark quantifier A?.

In particular, this article is all about the ? quantifier in Python’s re library.

What’s the Python Re ? Quantifier


When applied to regular expression A, Python’s A? quantifier matches either zero or one occurrences of A. The ? quantifier always applies only to the preceding regular expression. For example, the regular expression ‘hey?’ matches both strings ‘he’ and ‘hey’. But it does not match the empty string because the ? quantifier does not apply to the whole regex ‘hey’ but only to the preceding regex ‘y’.

Let’s study two basic examples to help you gain a deeper understanding. Do you get all of them?

>>> import re
>>>
>>> re.findall('aa[cde]?', 'aacde aa aadcde')
['aac', 'aa', 'aad']
>>>
>>> re.findall('aa?', 'accccacccac')
['a', 'a', 'a']
>>>
>>> re.findall('[cd]?[cde]?', 'ccc dd ee')
['cc', 'c', '', 'dd', '', 'e', 'e', '']

Don’t worry if you had problems understanding those examples. You’ll learn about them next. Here’s the first example:

>>> re.findall('aa[cde]?', 'aacde aa aadcde')
['aac', 'aa', 'aad']

You use the re.findall() method. In case you don’t know it, here’s the definition from the Finxter blog article:

The re.findall(pattern, string) method finds all occurrences of the pattern in the string and returns a list of all matching substrings.

Please consult the blog article to learn everything you need to know about this fundamental Python method.

The first argument is the regular expression pattern ‘aa[cde]?’. The second argument is the string to be searched for the pattern. In plain English, you want to find all patterns that start with two ‘a’ characters, followed by one optional character—which can be either ‘c’, ‘d’, or ‘e’.

The findall() method returns three matching substrings:

  • First, string ‘aac’ matches the pattern. After Python consumes the matched substring, the remaining substring is ‘de aa aadcde’.
  • Second, string ‘aa’ matches the pattern. Python consumes it which leads to the remaining substring ‘ aadcde’.
  • Third, string ‘aad’ matches the pattern in the remaining substring. What remains is ‘cde’ which doesn’t contain a matching substring anymore.

The second example is the following:

>>> re.findall('aa?', 'accccacccac')
['a', 'a', 'a']

In this example, you’re looking at the simple pattern ‘aa?’. You want to find all occurrences of character ‘a’ followed by an optional second ‘a’. But be aware that the optional second ‘a’ is not needed for the pattern to match.

Therefore, the regex engine finds three matches: the characters ‘a’.

The third example is the following:

>>> re.findall('[cd]?[cde]?', 'ccc dd ee')
['cc', 'c', '', 'dd', '', 'e', 'e', '']

This regex pattern looks complicated: ‘[cd]?[cde]?’. But is it really?

Let’s break it down step-by-step:

The first part of the regex [cd]? defines a character class [cd] which reads as “match either c or d”. The question mark quantifier indicates that you want to match either one or zero occurrences of this pattern.

The second part of the regex [cde]? defines a character class [cde] which reads as “match either c, d, or e”. Again, the question mark indicates the zero-or-one matching requirement.

As both parts are optional, the empty string matches the regex pattern. However, the Python regex engine attempts as much as possible.

Thus, the regex engine performs the following steps:

  • The first match in the string ‘ccc dd ee’ is ‘cc’. The regex engine consumes the matched substring, so the string ‘c dd ee’ remains.
  • The second match in the remaining string is the character ‘c’. The empty space ‘ ‘ does not match the regex so the second part of the regex [cde] does not match. Because of the question mark quantifier, this is okay for the regex engine. The remaining string is ‘ dd ee’.
  • The third match is the empty string ”. Of course, Python does not attempt to match the same position twice. Thus, it moves on to process the remaining string ‘dd ee’.
  • The fourth match is the string ‘dd’. The remaining string is ‘ ee’.
  • The fifth match is the string ”. The remaining string is ‘ee’.
  • The sixth match is the string ‘e’. The remaining string is ‘e’.
  • The seventh match is the string ‘e’. The remaining string is ”.
  • The eighth match is the string ”. Nothing remains.

This was the most complicated of our examples. Congratulations if you understood it completely!

[Collection] What Are The Different Python Re Quantifiers?


The question mark quantifier—Python re ?—is only one of many regex operators. If you want to use (and understand) regular expressions in practice, you’ll need to know all of them by heart!

So let’s dive into the other operators:

A regular expression is a decades-old concept in computer science. Invented in the 1950s by famous mathematician Stephen Cole Kleene, the decades of evolution brought a huge variety of operations. Collecting all operations and writing up a comprehensive list would result in a very thick and unreadable book by itself.

Fortunately, you don’t have to learn all regular expressions before you can start using them in your practical code projects. Next, you’ll get a quick and dirty overview of the most important regex operations and how to use them in Python. In follow-up chapters, you’ll then study them in detail — with many practical applications and code puzzles.

Here are the most important regex quantifiers:


Quantifier Description Example
. The wild-card (‘dot’) matches any character in a string except the newline character ‘\n’. Regex ‘…’ matches all words with three characters such as ‘abc’, ‘cat’, and ‘dog’.
* The zero-or-more asterisk matches an arbitrary number of occurrences (including zero occurrences) of the immediately preceding regex. Regex ‘cat*’ matches the strings ‘ca’, ‘cat’, ‘catt’, ‘cattt’, and ‘catttttttt’.
? The zero-or-one matches (as the name suggests) either zero or one occurrences of the immediately preceding regex. Regex ‘cat?’ matches both strings ‘ca’ and ‘cat’ — but not ‘catt’, ‘cattt’, and ‘catttttttt’.
+ The at-least-one matches one or more occurrences of the immediately preceding regex. Regex ‘cat+’ does not match the string ‘ca’ but matches all strings with at least one trailing character ‘t’ such as ‘cat’, ‘catt’, and ‘cattt’.
^ The start-of-string matches the beginning of a string. Regex ‘^p’ matches the strings ‘python’ and ‘programming’ but not ‘lisp’ and ‘spying’ where the character ‘p’ does not occur at the start of the string.
$ The end-of-string matches the end of a string. Regex ‘py$’ would match the strings ‘main.py’ and ‘pypy’ but not the strings ‘python’ and ‘pypi’.
A|B The OR matches either the regex A or the regex B. Note that the intuition is quite different from the standard interpretation of the or operator that can also satisfy both conditions. Regex ‘(hello)|(hi)’ matches strings ‘hello world’ and ‘hi python’. It wouldn’t make sense to try to match both of them at the same time.
AB  The AND matches first the regex A and second the regex B, in this sequence. We’ve already seen it trivially in the regex ‘ca’ that matches first regex ‘c’ and second regex ‘a’.

Note that I gave the above operators some more meaningful names (in bold) so that you can immediately grasp the purpose of each regex. For example, the ‘^’ operator is usually denoted as the ‘caret’ operator. Those names are not descriptive so I came up with more kindergarten-like words such as the “start-of-string” operator.

We’ve already seen many examples but let’s dive into even more!

import re text = ''' Ha! let me see her: out, alas! he's cold: Her blood is settled, and her joints are stiff; Life and these lips have long been separated: Death lies on her like an untimely frost Upon the sweetest flower of all the field. ''' print(re.findall('.a!', text)) '''
Finds all occurrences of an arbitrary character that is
followed by the character sequence 'a!'.
['Ha!'] ''' print(re.findall('is.*and', text)) '''
Finds all occurrences of the word 'is',
followed by an arbitrary number of characters
and the word 'and'.
['is settled, and'] ''' print(re.findall('her:?', text)) '''
Finds all occurrences of the word 'her',
followed by zero or one occurrences of the colon ':'.
['her:', 'her', 'her'] ''' print(re.findall('her:+', text)) '''
Finds all occurrences of the word 'her',
followed by one or more occurrences of the colon ':'.
['her:'] ''' print(re.findall('^Ha.*', text)) '''
Finds all occurrences where the string starts with
the character sequence 'Ha', followed by an arbitrary
number of characters except for the new-line character. Can you figure out why Python doesn't find any?
[] ''' print(re.findall('\n$', text)) '''
Finds all occurrences where the new-line character '\n'
occurs at the end of the string.
['\n'] ''' print(re.findall('(Life|Death)', text)) '''
Finds all occurrences of either the word 'Life' or the
word 'Death'.
['Life', 'Death'] '''

In these examples, you’ve already seen the special symbol ‘\n’ which denotes the new-line character in Python (and most other languages). There are many special characters, specifically designed for regular expressions. Next, we’ll discover the most important special symbols.

What’s the Difference Between Python Re ? and * Quantifiers?


You can read the Python Re A? quantifier as zero-or-one regex: the preceding regex A is matched either zero times or exactly once. But it’s not matched more often.

Analogously, you can read the Python Re A* operator as the zero-or-multiple-times regex (I know it sounds a bit clunky): the preceding regex A is matched an arbitrary number of times.

Here’s an example that shows the difference:

>>> import re
>>> re.findall('ab?', 'abbbbbbb')
['ab']
>>> re.findall('ab*', 'abbbbbbb')
['abbbbbbb']

The regex ‘ab?’ matches the character ‘a’ in the string, followed by character ‘b’ if it exists (which it does in the code).

The regex ‘ab*’ matches the character ‘a’ in the string, followed by as many characters ‘b’ as possible.

What’s the Difference Between Python Re ? and + Quantifiers?


You can read the Python Re A? quantifier as zero-or-one regex: the preceding regex A is matched either zero times or exactly once. But it’s not matched more often.

Analogously, you can read the Python Re A+ operator as the at-least-once regex: the preceding regex A is matched an arbitrary number of times but at least once.

Here’s an example that shows the difference:

>>> import re
>>> re.findall('ab?', 'aaaaaaaa')
['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a']
>>> re.findall('ab+', 'aaaaaaaa')
[]

The regex ‘ab?’ matches the character ‘a’ in the string, followed by character ‘b’ if it exists—but it doesn’t in the code.

The regex ‘ab+’ matches the character ‘a’ in the string, followed by as many characters ‘b’ as possible—but at least one. However, the character ‘b’ does not exist so there’s no match.

What are Python Re *?, +?, ?? Quantifiers?


You’ve learned about the three quantifiers:

  • The quantifier A* matches an arbitrary number of patterns A.
  • The quantifier A+ matches at least one pattern A.
  • The quantifier A? matches zero-or-one pattern A.

Those three are all greedy: they match as many occurrences of the pattern as possible. Here’s an example that shows their greediness:

>>> import re
>>> re.findall('a*', 'aaaaaaa')
['aaaaaaa', '']
>>> re.findall('a+', 'aaaaaaa')
['aaaaaaa']
>>> re.findall('a?', 'aaaaaaa')
['a', 'a', 'a', 'a', 'a', 'a', 'a', '']

The code shows that all three quantifiers *, +, and ? match as many ‘a’ characters as possible.

So, the logical question is: how to match as few as possible? We call this non-greedy matching. You can append the question mark after the respective quantifiers to tell the regex engine that you intend to match as few patterns as possible: *?, +?, and ??.

Here’s the same example but with the non-greedy quantifiers:

>>> import re
>>> re.findall('a*?', 'aaaaaaa')
['', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '']
>>> re.findall('a+?', 'aaaaaaa')
['a', 'a', 'a', 'a', 'a', 'a', 'a']
>>> re.findall('a??', 'aaaaaaa')
['', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '', 'a', '']

In this case, the code shows that all three quantifiers *?, +?, and ?? match as few ‘a’ characters as possible.

Related Re Methods


There are five important regular expression methods which you should master:

  • The re.findall(pattern, string) method returns a list of string matches. Read more in our blog tutorial.
  • The re.search(pattern, string) method returns a match object of the first match. Read more in our blog tutorial.
  • The re.match(pattern, string) method returns a match object if the regex matches at the beginning of the string. Read more in our blog tutorial.
  • The re.fullmatch(pattern, string) method returns a match object if the regex matches the whole string. Read more in our blog tutorial.
  • The re.compile(pattern) method prepares the regular expression pattern—and returns a regex object which you can use multiple times in your code. Read more in our blog tutorial.
  • The re.split(pattern, string) method returns a list of strings by matching all occurrences of the pattern in the string and dividing the string along those. Read more in our blog tutorial.
  • The re.sub(The re.sub(pattern, repl, string, count=0, flags=0) method returns a new string where all occurrences of the pattern in the old string are replaced by repl. Read more in our blog tutorial.

These seven methods are 80% of what you need to know to get started with Python’s regular expression functionality.

Where to Go From Here?


You’ve learned everything you need to know about the question mark quantifier ? in this regex tutorial.

Summary: When applied to regular expression A, Python’s A? quantifier matches either zero or one occurrences of A. The ? quantifier always applies only to the preceding regular expression. For example, the regular expression ‘hey?’ matches both strings ‘he’ and ‘hey’. But it does not match the empty string because the ? quantifier does not apply to the whole regex ‘hey’ but only to the preceding regex ‘y’.

Want to earn money while you learn Python? Average Python programmers earn more than $50 per hour. You can certainly become average, can’t you?

Join the free webinar that shows you how to become a thriving coding business owner online!

[Webinar] Become a Six-Figure Freelance Developer with Python

Join us. It’s fun! ?



https://www.sickgaming.net/blog/2020/01/...uantifier/

Print this item

  [Tut] Double Opt-In Subscription Form with Secure Hash using PHP
Posted by: xSicKxBot - 01-27-2020, 02:17 AM - Forum: PHP Development - No Replies

Double Opt-In Subscription Form with Secure Hash using PHP

Last modified on September 24th, 2019 by Vincy.

Do you know that the opening rate of emails by double opt-in confirmed subscribers is a staggering 40%? According to CampaignMonitor, email marketing generates $38 in ROI for every $1 spent.

Email marketing delivers the highest among any channel for marketing. Even in comparison with channels like print, TV and social media.

Double Opt-In Subscription Form with Secure Hash using PHP

Email marketing is the way to go. The primary mode to build your list is using a double opt-in subscription form.

What is inside?


  1. Why do we need double opt-in?
  2. What is the role of secure hash?
  3. Double opt-in subscription form in PHP
  4. Sequence flow for double opt-in subscription
  5. Double opt-in Subscription form UI
  6. PHP AJAX for subscription form submission
  7. URL with secure hash
  8. A PHP utility class for you
  9. Store subscription information to database
  10. A database abstraction layer for you
  11. Send confirmation email to users
  12. Subscription confirmation
  13. Conclusion

Use a double opt-in subscription form to signup to a newsletter, blog or a similar service. It has a two-step subscription process.

In the first step, the user will submit his name and email. Then the site will send an email to the user.

In the second step, the user will click the link in the received email. This will confirm his subscription to the site or service.

We call it the double opt-in because the users consent to the subscription twice. First by submitting the information and second by confirming to in by clicking the link in email.

Why do we need double opt-in?


It is the mechanism used to verify if the subscriber owns the input email. You need to do this verification because there is a chance for misuse by submitting emails that they do not own.

Double opt-in vs single opt-in is well debated and results arrived at. Double opt-in wins hands-on in every critical aspect.

What is the role of a secure hash?


In the confirmation email received by the user, there will be a link. This is the second and important step in the opt-in process. The link should be secure.

  • It should be unique for every user and request.
  • It should not be predictable.
  • It should be immune to a brute-force attack.

Double opt-in subscription form in PHP


I will present you a step by step detail on how to build a double opt-in subscription form with a secure hash using PHP.

You will get a production-grade code which you can use real-time in your live website. You can use this to manage your newsletter subscription.

I am releasing this code to you under MIT license. You can use it free even in commercial projects.

Sequence flow for double opt-in subscription


  1. Show a subscription form to the user.
  2. On AJAX submit, insert a new record in the database.
  3. Send an email to the user with a secure hash link.
  4. On click, the of the link, update the subscription status.
  5. On every step, there will be appropriate validations in place.

Double opt-in Subscription form UI


This is where developers get it wrong. Keep it simple and unobtrusive. For the high conversion, you must keep in minimal.

One field email is enough for the subscription. To address the user in a personal way, you need their name. That’s it. Do not ask for much information on a subscription form.

<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="author" content="Vincy"> <link rel="stylesheet" type="text/css" href="assets/css/phppot-style.css"> <title>Double Opt-In Subscription Form with Secure Hash using PHP</title> </head> <body> <div class="phppot-container"> <h1>Double Opt-in Subscription</h1> <form class="phppot-form" action="" method="POST"> <div class="phppot-row"> <div class="label"> Name </div> <input type="text" id="pp-name" name="pp-name" class="phppot-input"> </div> <div class="phppot-row"> <div class="label"> Email * <div id="email-info" class="validation-message" data-required-message="required." data-validate-message="Invalid email."></div> </div> <input type="text" id="pp-email" name="pp-email" class="required email phppot-input" on‌focusout="return validateEmail();"> </div> <div class="phppot-row"> <button type="Submit" id="phppot-btn-send">Subscribe</button> <div id="phppot-loader-icon">Sending ...</div> <div id="phppot-message"></div> </div> </form> </div> <script src="vendor/jquery/jquery-3.3.1.js"></script> <script src="assets/js/subscribe.js"></script></body> </body> </html> 

If you ask for much information, it will drive your users away. The same principle applies when you build a contact form. More or less these two behave in a similar aspect. Check how to build a contact form to know more on it.

Double Opt-in subscription form UI

You should leave the name field can as optional and only the email field should be as required. This will encourage the user to submit the form and subscribe for the newsletter.

Needless to say, the form should be responsive. Any page or form you build should work in mobile, tablet, laptop and desktop devices. You should optimize to work on any viewport.

Google parses webpages in mobile mode for indexing in the search result. The desktop is an old story and gone are those days. You should always design for the mobile. Make it mobile-first!

PHP AJAX for subscription form submission


I have used AJAX to manage the submission. This will help the user to stay on the page after subscription. You can position this subscription form in a sidebar or the footer.

Double Opt-in Subscription Form AJAX Submission

This is a classic example of where you should use the AJAX. I have seen instances where people use AJAX in inappropriate places, for the sake of using it.

Subscription AJAX endpoint


The AJAX endpoint has three major steps:

  1. Verify the user input.
  2. Insert a record in the database.
  3. Send an email with a link for subscription.

subscribe-ep.php is the AJAX endpoint. It starts with an if condition to check if the submit is via the POST method. It is always good to program for POST instead of the GET by default.

<?php use Phppot\Subscription; use Phppot\SupportService; /** * AJAX end point for subscribe action. * 1. validate the user input * 2. store the details in database * 3. send email with link that has secure hash for opt-in confirmation */ session_start(); // to ensure the request via POST if ($_POST) { require_once __DIR__ . './../lib/SupportService.php'; $supportService = new SupportService(); // to Debug set as true $supportService->setDebug(false); // to check if its an ajax request, exit if not $supportService->validateAjaxRequest(); require_once __DIR__ . './../Model/Subscription.php'; $subscription = new Subscription(); // get user input and sanitize if (isset($_POST["pp-email"])) { $userEmail = trim($_POST["pp-email"]); $userEmail = filter_var($userEmail, FILTER_SANITIZE_EMAIL); $subscription->setEmail($userEmail); } else { // server side fallback validation to check if email is empty $output = $supportService->createJsonInstance('Email is empty!'); $supportService->endAction($output); } $memberName = ""; if (isset($_POST["pp-name"])) { $memberName = filter_var($_POST["pp-name"], FILTER_SANITIZE_STRING); } $subscription->setMemberName($memberName); // 1. get a 12 char length random string token $token = $supportService->getToken(12); // 2. make that random token to a secure hash $secureToken = $supportService->getSecureHash($token); // 3. convert that secure hash to a url string $urlSecureToken = $supportService->cleanUrl($secureToken); $subscription->setSubsriptionKey($urlSecureToken); $subscription->setSubsciptionSatus(0); $currentTime = date("Y-m-d H:i:s"); $subscription->setCreateAt($currentTime); $result = $subscription->insert(); // check if the insert is success // if success send email else send message to user $messageType = $supportService->getJsonValue($result, 'type'); if ('error' != $messageType) { $result = $subscription->sendConfirmationMessage($userEmail, $urlSecureToken); } $supportService->endAction($result); } 

I have used the SupportService class to perform common functions.

Input sanitisation is a must. When you collect information using a public website, you should be careful. You could get infected without your knowledge. There are many bots foraging around the Internet and they click on all links and buttons.

To sanitise, do not invent a new function. Use the function provided by PHP and that is safe to use.

URL with secure hash


Generate a unique url for each user subscription. Use this url to confirm the user’s subscription in the second step. Remember, that’s why we call this double opt-in.

I have used a three step process:

  1. Generate a random string token.
  2. Convert the token to secure hash.
  3. Convert the secure hash to safe url.

I have used hexdec, bin2hex and openssl_random_pseudo_bytes to generate random bits. Which forms a random string.

Then to make the random string a secure hash, I have used the PHP’s built-in password_hash function. Never every try to do something on your own. Go with the PHP’s function and it does the job very well.

Before PHP 7, we had the option to supply a user generated salt. Now PHP 7 release has deprecated it. It is a good move because, PHP can generate a better salt than what you will generate. So stick to PHP 7 and use it without supplying your own salt.

The secure hash will contain all sort of special characters. . You can keep those special characters but need to url encode it. But I always wish to keep urls clean and the encoded chars do not look nice.

So no harm in removing them. So I cleanup those and leave only the safe characters. Then as a secondary precaution, I also encode the resultant string.

Thus after going through multi step process, we get a random, hash secure, safe, encoded URL token. Save the user submitted information in database record along with this token.

A PHP utility class for you


This is a utility class which I use in my projects. I am giving it away free for you all. It has functions that I reuse quite often and will be handy in situations. Every method has detailed comments that explain their purpose and usage method.

<?php /** * Copyright © 2019 Phppot * * Distributed under MIT license with an exception that, * you don’t have to include the full MIT License in your code. * In essense, you can use it on commercial software, modify and distribute free. * Though not mandatory, you are requested to attribute this URL in your code or website. */ namespace Phppot; class SupportService { /** * Short circuit type function to stop the process flow on validation failure. */ public function validateAjaxRequest() { // to check if its an ajax request, exit if not $http_request = $_SERVER['HTTP_X_REQUESTED_WITH']; if (! isset($http_request) && strtolower($http_request) != 'xmlhttprequest') { $output = $this->createJsonInstance('Not a valid AJAX request!'); $this->endAction($output); } } /** * Last point in the AJAX work flow. * Clearing tokens, handles and resource cleanup can be done here. * * @param string $output * @param boolean $clearToken */ public function endAction($output) { die($output); } public function setDebug($mode) { if ($mode == true) { ini_set('display_errors', 1); set_error_handler(function ($severity, $message, $file, $line) { if (error_reporting() & $severity) { throw new \ErrorException($message, 0, $severity, $file, $line); } }); } } /** * encodes a message string into a json object * * @param string $message * @param string $type * @return \JsonSerializable encoded json object */ public function createJsonInstance($message, $type = 'error') { $messageArray = array( 'type' => $type, 'text' => $message ); $jsonObj = json_encode($messageArray); return $jsonObj; } public function getJsonValue($json, $key) { $jsonArray = json_decode($json, true); return $jsonArray[$key]; } /** * If you are using PHP, this is the best possible secure hash * do not try to implement somthing on your own * * @param string $text * @return string */ public function getSecureHash($text) { $hashedText = password_hash($text, PASSWORD_DEFAULT); return $hashedText; } /** * generates a random token of the length passed * * @param int $length * @return string */ public function getToken($length) { $token = ""; $codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; $codeAlphabet .= "abcdefghijklmnopqrstuvwxyz"; $codeAlphabet .= "0123456789"; $max = strlen($codeAlphabet) - 1; for ($i = 0; $i < $length; $i ++) { $token .= $codeAlphabet[$this->cryptoRandSecure(0, $max)]; } return $token; } public function cryptoRandSecure($min, $max) { $range = $max - $min; if ($range < 1) { return $min; // not so random... } $log = ceil(log($range, 2)); $bytes = (int) ($log / 8) + 1; // length in bytes $bits = (int) $log + 1; // length in bits $filter = (int) (1 << $bits) - 1; // set all lower bits to 1 do { $rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes))); $rnd = $rnd & $filter; // discard irrelevant bits } while ($rnd >= $range); return $min + $rnd; } /** * makes the passed string url safe and return encoded url * * @param string $str * @return string */ public function cleanUrl($str, $isEncode = 'true') { $delimiter = "-"; $str = str_replace(' ', $delimiter, $str); // Replaces all spaces with hyphens. $str = preg_replace('/[^A-Za-z0-9\-]/', '', $str); // allows only alphanumeric and - $str = trim($str, $delimiter); // remove delimiter from both ends $regexConseqChars = '/' . $delimiter . $delimiter . '+/'; $str = preg_replace($regexConseqChars, $delimiter, $str); // remove consequtive delimiter $str = mb_strtolower($str, 'UTF-8'); // convert to all lower if ($isEncode) { $str = urldecode($str); // encode to url } return $str; } /** * to mitigate XSS attack */ public function xssafe($data, $encoding = 'UTF-8') { return htmlspecialchars($data, ENT_QUOTES | ENT_HTML401, $encoding); } /** * convenient method to print XSS mitigated text * * @param string $data */ public function xecho($data) { echo $this->xssafe($data); } } 

Store subscription information to the database


Insert a record to the database on submission of the subscription form. We get the user’s name, email, generate a secure hash token, current time, subscription status.

<?php /** * Copyright © 2019 Phppot * * Distributed under MIT license with an exception that, * you don’t have to include the full MIT License in your code. * In essense, you can use it on commercial software, modify and distribute free. * Though not mandatory, you are requested to attribute this URL in your code or website. */ namespace Phppot; use Phppot\DataSource; class Subscription { private $ds; private $memberName; private $email; private $subsriptionKey; private $subsciptionSatus; private $createAt; private $supportService; function __construct() { require_once __DIR__ . './../lib/DataSource.php'; $this->ds = new DataSource(); require_once __DIR__ . './../lib/SupportService.php'; $this->supportService = new SupportService(); } public function getMemberName() { return $this->memberName; } public function getEmail() { return $this->email; } public function getSubsriptionKey() { return $this->subsriptionKey; } public function getSubsciptionSatus() { return $this->subsciptionSatus; } public function getCreateAt() { return $this->createAt; } public function setMemberName($memberName) { $this->memberName = $memberName; } public function setEmail($email) { $this->email = $email; } public function setSubsriptionKey($subsriptionKey) { $this->subsriptionKey = $subsriptionKey; } public function setSubsciptionSatus($subsciptionSatus) { $this->subsciptionSatus = $subsciptionSatus; } public function setCreateAt($createAt) { $this->createAt = $createAt; } /** * to get the member record based on the subscription_key * * @param string $subscriptionKey * @return array result record */ public function getMember($subscriptionKey, $subscriptionStatus) { $query = 'SELECT * FROM tbl_subscription where subscription_key = ? and subscription_status = ?'; $paramType = 'si'; $paramValue = array( $subscriptionKey, $subscriptionStatus ); $result = $this->ds->select($query, $paramType, $paramValue); return $result; } public function insert() { $query = 'INSERT INTO tbl_subscription (member_name, email, subscription_key, subscription_status, create_at) VALUES (?, ?, ?, ?, ?)'; $paramType = 'sssis'; $paramValue = array( $this->memberName, $this->email, $this->subsriptionKey, $this->subsciptionSatus, $this->createAt ); $insertStatus = $this->ds->insert($query, $paramType, $paramValue); return $insertStatus; } public function updateStatus($subscriptionKey, $subscriptionStatus) { $query = 'UPDATE tbl_subscription SET subscription_status = ? WHERE subscription_key = ?'; $paramType = 'is'; $paramValue = array( $subscriptionStatus, $subscriptionKey ); $this->ds->execute($query, $paramType, $paramValue); } /** * sends confirmation email, to keep it simple, I am just using the PHP's mail * I reccommend serious users to change it to PHPMailer and set * appropriate headers */ public function sendConfirmationMessage($mailTo, $urlSecureToken) { // following is the opt-in url that will be sent in email to // the subscriber. Replace example.com with your server $confirmOptInUrl = 'http://example.com/confirm.php?q=' . $urlSecureToken; $message = '<p>Howdy!</p> <p>This is an automated message sent for subscription service. You must confirm your request to subscribe to example.com site.</p> <p>Website Name: example</p> <p>Website URL: http://example.com</p> <p>Click the following link to confirm: ' . $confirmOptInUrl . '</p>'; $isSent = mail($mailTo, 'Confirm your subscription', $message); if ($isSent) { $message = "An email is sent to you. You should confirm the subscription by clicking the link in the email."; $result = $this->supportService->createJsonInstance($message, 'message'); } else { $result = $this->supportService->createJsonInstance('Error in sending confirmation email.', 'error'); } return $result; } } 

The reason for storing the current time is to have an expiry for every link. We can set a predefined expiry for the double opt-in process.

For example, you can set one week as expiry for a link from the moment you generate it. The user has to click and confirm before that expiry period.

Subscription status is by default stored as ‘0’ and on confirmation changed to ‘1’.

A database abstraction layer for you


It is my PHP abstraction for minor projects. This works as a layer between controller, business logic and the database. It has generic methods using which we can to the CRUD operations. I have bundled it with the free project download that is available at the end of this tutorial.

Send confirmation email to users


After you insert the record, send an email will to the user to perform the double opt-in confirmation. The user will have a link in the email which he has to click to confirm.

Keep the email simple. It is okay to have text instead of fancy HTML emails. PHP is capable of generating any email and you can code complex email templates. But the spam engines may not like it.

Subscription information database record with secure hash

If you wish to go with HTML emails, then keep the HTML code ratio to as least as possible. As this is also one factor using which the spam engines flag the emails.

Then remember not to use the spam stop words. There are words like “free”, “win”, “cash”, “promo” and “income”. There is a long list and you can get it on the Internet by searching for “email spam filter word list”.

I have used PHP’s mail() function to send the email. I recommend you to change it to PHPMailer to send SMTP based email if you plan to use this code in production.

Subscription confirmation


Create a public landing page and you may use .htaccess for a neat URL mapping. This URL should map with the URL sent to the user and the PHP file that is going to process the request.

As a first step, GET the token and to verify the user against the database. Check,

  1. if such a token exists,
  2. it is not expired,
  3. the user is not already subscribed
  4. add more validation as you deem fit.
<?php use Phppot\Subscription; use Phppot\SupportService; /** * For confirmation action. * 1. Get the secure has from url * 2. validate it against url * 3. update the subscription status in database accordingly. */ session_start(); // to ensure the request via POST require_once __DIR__ . '/lib/SupportService.php'; $supportService = new SupportService(); // to Debug set as true $supportService->setDebug(true); $subscriptionKey = $_GET['q']; require_once __DIR__ . '/Model/Subscription.php'; $subscription = new Subscription(); $result = $subscription->getMember($subscriptionKey, 0); if (count($result) > 0) { // member found, go ahead and update status $subscription->updateStatus($subscriptionKey, 1); $message = $result[0]['member_name'] . ', your subscription is confirmed.'; $messageType = 'success'; } else { // securiy precaution: do not reveal any information here // play subtle with the reported message $message = 'Invalid URL!'; $messageType = 'error'; } ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="author" content="Vincy"> <link rel="stylesheet" type="text/css" href="assets/css/phppot-style.css"> <title>Double Opt-In Subscription Confirmation</title> </head> <body> <div class="phppot-container"> <h1>Double Opt-in Subscription Confirmation</h1> <div class="phppot-row"> <div id="phppot-message" class="<?php echo $messageType; ?>"><?php echo $message;?></div> </div> </div> </body> </body> </html> 

If validation fails, do not reveal any information to the user. You should only say that it has failed.

Subscription double opt-in confirmation url verification

More important do not say, not such email found. This will allow finding who has subscribed to your service. Whenever a validation fails, the displayed message should not reveal internal information.

On validation success, update the subscription status. Then show a happy success message to the user.

Conclusion


I have presented you with a production-grade double opt-in subscription form. I have followed a most secure hash generation method for confirmation URL email. I present it to you under the MIT license. The intention is to be the most permissible. You can download it free and change the code. You can even use it in your commercial projects. I have used the most secure code as possible. You can use this in your live site to manage newsletter subscription. In the coming part, I will include unsubscribe and enhance it further. Leave your comments below with what sort of enhancements you are looking for.

Download

↑ Back to Top



https://www.sickgaming.net/blog/2019/09/...using-php/

Print this item

  (Indie Deal) Dharker Studio Bundle & Paradox Interactive Flash Sale
Posted by: xSicKxBot - 01-27-2020, 02:17 AM - Forum: Deals or Specials - No Replies

Dharker Studio Bundle & Paradox Interactive Flash Sale

Dharker Studio Bundle | 11 Steam Games | 96% OFF
[www.indiegala.com]
Hot story driven games, soundtracks and plenty of romantic dates: the Dharker Studio Bundle has you covered. Don't miss the special 24h launch price.

Paradox Interactive Flash Sale, up to -85%
[www.indiegala.com]
Level up your Steam library with BATTLETECH, Cities: Skylines, Age of Wonders, Stellaris and more exclusive deals from Paradox Interactive.

Final day for this Crackerjack Deal.[www.indiegala.com]

Happy Hour
Today's Happy Hour is LIVE for Shade & Hate Bundle[www.indiegala.com]!

Check out IndieGala on Twitter, YouTube & Facebook[www.facebook.com]


https://steamcommunity.com/groups/indieg...8732121993

Print this item