Created
September 12, 2018 04:51
-
-
Save chronologicaldot/dd9729660f3dd843c5e776094cfe3309 to your computer and use it in GitHub Desktop.
Tic Tac Toe game in Copper for Cupric Bridge
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Utilities # | |
makeVect = [x y] { | |
ret([x=x y=y]) | |
} | |
movedVectX = [vect x]{ | |
vect.x = +(vect.x: x:) | |
ret(vect) | |
} | |
movedVectY = [vect y]{ | |
vect.y = +(vect.y: y:) | |
ret(vect) | |
} | |
makeRect = [x y x2 y2] { | |
ret([x=x y=y x2=x2 y2=y2]) | |
} | |
movedRect = [rect shift] { | |
ret([ | |
x=+(rect.x: shift.x:) | |
y=+(rect.y: shift.y:) | |
x2=+(rect.x2: shift.x:) | |
y2=+(rect.y2: shift.y:) | |
]) | |
} | |
foreach = [items do] { | |
ml = member_list(items) | |
mllen = length(ml:) | |
i = 0 | |
loop { | |
if ( equal(i: mllen:) ) { stop } | |
do(member(items: item_at(ml: i:))) | |
++(i:) | |
} | |
} | |
forlist = [items do] { | |
len = length(items:) | |
i = 0 | |
loop { | |
if ( equal(i: len:) ) { stop } | |
do(item_at(items: i:)) | |
++(i:) | |
} | |
} | |
for = [count do] { | |
i = 0 | |
loop { | |
if ( gte(i: count:) ) { stop } | |
do(copy_of(i)) # copy_of prevents modding i # | |
++(i:) | |
} | |
} | |
leaper = [leap]{ | |
ret([i=0 leap=leap]{ | |
i2 = this.i | |
this.i = +(this.i: this.leap:) | |
ret(i2:) | |
}) | |
} | |
buttonInfo = [ | |
Visible = true | |
Enabled = true | |
Border = true | |
UseAlphaChannel = true | |
#ScaleImage = false# | |
] | |
scalingButtonInfo = [ | |
LeftAlign = "scale" | |
RightAlign = "scale" | |
TopAlign = "scale" | |
BottomAlign = "scale" | |
] | |
isEven = [v]{ | |
ret(equal(%(v: 2) 0)) | |
} | |
# Specific details # | |
screenSize = gui_position(gui_root:) | |
screenSize.width = screenSize.x2 | |
screenSize.height = screenSize.y2 | |
gameDefaults = [ | |
slotSize = 200 | |
] | |
gameBoard # GUI element (wrapping element) # | |
gameTable # list of 9 slots # | |
gameOver = false | |
mainMenuBar # GUI element # | |
gameSettings = [ | |
AIenabled = false | |
AIlevel = 1 | |
] | |
app = [ | |
closeID = 100 | |
saveGameID = 101 | |
loadGameID = 102 | |
newGameID = 103 | |
AIvsHumanID = 104 | |
AILevelID = 105 | |
bannerID = 50 | |
] | |
getGameImage = [imageName] { | |
ret(get_texture(concat("./img/" imageName: ".png"))) | |
} | |
getCloseImage = { ret(getGameImage("exit")) } | |
getSaveGameImage = { ret(getGameImage("save")) } | |
getLoadGameImage = { ret(getGameImage("load")) } | |
getNewGameImage = { ret(getGameImage("new")) } | |
getHumanvsHumanImage = { ret(getGameImage("human_vs_human")) } | |
getAIvsHumanImage = { ret(getGameImage("ai_vs_human")) } | |
getEmptySlotImage = { ret(getGameImage("q")) } | |
getOSlotImage = { ret(getGameImage("circle")) } | |
getXSlotImage = { ret(getGameImage("x")) } | |
getPlayer1WinsBanner = { ret(getGameImage("player1wins")) } | |
getPlayer2WinsBanner = { ret(getGameImage("player2wins")) } | |
getDrawBanner = { ret(getGameImage("draw")) } | |
menuBarHeight = { | |
ret(-(screenSize.height: *(gameDefaults.slotSize: 3))) | |
} | |
makeMenuBar = { | |
ret(gui_create("toolBar" [ | |
Rect = makeRect(0 0 screenSize.width menuBarHeight:) | |
Visible = true | |
Enabled = true | |
RightAlign = "scale" | |
BottomAlign = "scale" | |
])) | |
} | |
makeMenuButton = [pos size id image parent tooltip]{ | |
b = gui_create("button" union(buttonInfo [ | |
Rect = makeRect(pos.x: pos.y: +(pos.x: size:) +(pos.y: size:)) | |
LeftAlign = "scale" | |
RightAlign = "scale" | |
BottomAlign = "lowerRight" | |
Id = id | |
Image = image | |
#ScaleImage = true# | |
ToolTip = tooltip | |
])) | |
gui_parent(b: parent:) | |
ret(b:) | |
} | |
makeMenuAIDropDown = [pos size id parent] { | |
info = [ | |
Visible = true | |
Enabled = true | |
Rect = makeRect(pos.x: pos.y: +(pos.x: *(size: 4)) +(pos.y: size:)) | |
LeftAlign = "scale" | |
RightAlign = "scale" | |
BottomAlign = "lowerRight" | |
Id = id | |
NoClip = true | |
ItemCount = 4 | |
Item0Text = "Basic AI" | |
Item0Enabled = true | |
Item1Text = "Decent AI" | |
Item1Enabled = true | |
Item2Text = "Hard AI" | |
Item2Enabled = true | |
Item3Text = "Impossible AI" | |
Item3Enabled = true | |
] | |
e = gui_create("dropdown" info) | |
gui_parent(e: parent:) | |
} | |
gameButtonInfo = [ | |
basics = [ | |
Visible = true | |
Enabled = true | |
UseAlphaChannel = true | |
#ScaleImage = true# | |
Border = true | |
] | |
rect = [slotPos]{ | |
x = *(slotPos.x: gameDefaults.slotSize:) | |
y = *(slotPos.y: gameDefaults.slotSize:) | |
x2 = +(x: gameDefaults.slotSize:) | |
y2 = +(y: gameDefaults.slotSize:) | |
ret(makeRect(x y x2 y2)) | |
} | |
] | |
makeGameBoard = { | |
iToXY = [i] { | |
ret([ x = %(i: 3), y = /(i: 3) ]) | |
} | |
gameBoard = gui_new_empty([ | |
Rect = makeRect(0 menuBarHeight: screenSize.width: screenSize.height:) | |
Visible = true | |
Enabled = true | |
RightAlign = "lowerRight" | |
TopAlign = "scale" | |
BottomAlign = "lowerRight" | |
]) | |
for(9 [i iToXY~iToXY] { | |
b = gui_create("button") | |
gui_parent(b: gameBoard:) | |
gui_attrs(b: union(gameButtonInfo.basics scalingButtonInfo [ | |
Rect=gameButtonInfo.rect(this.iToXY(i)) | |
Id = +(i: 1) | |
Image = getEmptySlotImage: | |
] )) | |
}) | |
} | |
buildGUI = { | |
mainMenuBar = makeMenuBar: | |
size = -(menuBarHeight: 20) | |
shift = +(size: 10) | |
l = leaper(size) | |
pos = makeVect(10 10) | |
makeMenuButton(pos size app.newGameID getNewGameImage: mainMenuBar "New Game") | |
pos = movedVectX(pos shift) | |
b = makeMenuButton(pos size app.AIvsHumanID getHumanvsHumanImage: mainMenuBar "Switch between AI vs Human and Human vs Human") | |
gui_value(b: [PushButton=true Pressed=false]) | |
pos = movedVectX(pos shift) | |
makeMenuButton(pos size app.closeID getCloseImage: mainMenuBar "Exit Game") | |
pos = movedVectX(pos shift) | |
makeMenuAIDropDown(pos size app.AILevelID mainMenuBar) | |
makeGameBoard: | |
# Victory banner # | |
ssw2 = /(screenSize.width: 2) | |
ssh2 = /(screenSize.height: 2) | |
banner = gui_create("image" [ | |
Rect = makeRect(-(ssw2: 128) -(ssh2: 128) +(ssw2: 128) +(ssh2: 128)) | |
Id = app.bannerID | |
Visible = false | |
Enabled = false | |
LeftAlign = "center" | |
RightAlign = "center" | |
TopAlign = "center" | |
BottomAlign = "center" | |
UseAlphaChannel = true | |
#ScaleImage = true# | |
Color = [red=255 green=255 blue=255 alpha=255] | |
Image = getPlayer1WinsBanner: | |
]) | |
gui_parent(banner: gameBoard:) | |
} | |
buildGUI: | |
playerTurnSequence = [playerCount]{ | |
ret([ | |
playerCount = playerCount: | |
current = 1 | |
reset = { super.current = 1 } | |
]{ | |
++(this.current:) | |
if ( gt(this.current: this.playerCount:) ) { | |
this.current = 1 | |
} | |
}) | |
} | |
playerTurn = playerTurnSequence(2) | |
displayWinnerBanner = [winnerID] { | |
banner = gui_child_with_id(gameBoard: app.bannerID:) | |
if ( equal(winnerID: 1) ) { | |
gui_value(banner: [Texture=getPlayer1WinsBanner:]) | |
} | |
elif ( equal(winnerID: 2) ) { | |
gui_value(banner: [Texture=getPlayer2WinsBanner:]) | |
} | |
else { | |
gui_value(banner: [Texture=getDrawBanner:]) | |
} | |
gui_visible(banner: true) | |
} | |
clearWinnerBanner = { | |
banner = gui_child_with_id(gameBoard: app.bannerID:) | |
gui_visible(banner: false) | |
} | |
resetGameTable = { | |
gameTable = list(0 0 0 0 0 0 0 0 0) | |
} | |
resetGameTable: | |
resetGame = { | |
clearWinnerBanner: | |
for(9 [i]{ | |
b = gui_child_with_id(gameBoard: +(i: 1)) | |
info = gameButtonInfo.basics | |
info.Image = getEmptySlotImage: | |
gui_specifics(b: info) | |
}) | |
resetGameTable: | |
playerTurn.reset: | |
gameOver = false | |
} | |
isGameBoardFull = { | |
b.count = 0 | |
for(length(gameTable:) [i b~b]{ | |
if ( gt(item_at(gameTable: i:) 0) ) { | |
++(this.b.count:) | |
} | |
}) | |
ret(equal(b.count: length(gameTable:))) | |
} | |
getPlayerImageFromID = [player] { | |
if ( equal(player: 1) ) { | |
ret(getOSlotImage:) | |
} | |
else { | |
ret(getXSlotImage:) | |
} | |
} | |
# The button IDs are 1-9 # | |
markButton = [elem_id player]{ | |
tableSlot = -(elem_id: 1) | |
if ( gt(item_at(gameTable: tableSlot:) 0) ) { | |
ret(false) | |
} | |
replace(gameTable: tableSlot: player:) | |
b = gui_child_with_id(gameBoard: elem_id:) | |
info = gameButtonInfo.basics | |
info.Image = getPlayerImageFromID(player) | |
gui_specifics(b: info) | |
ret(true) | |
} | |
tableSequences = list( | |
list(0 1 2) list(3 4 5) list(6 7 8) | |
list(0 3 6) list(1 4 7) list(2 5 8) | |
list(0 4 8) list(2 4 6) | |
) | |
findWinner = { | |
winner.id = 0 | |
tableSel = [idx] { | |
ret(item_at(gameTable: idx:)) | |
} | |
checkSequence = [seq t~tableSel] { | |
ts = [idx t~this.t s~seq]{ | |
ret(this.t(item_at(this.s: idx:))) | |
} | |
who = ts(0) | |
who.same = true | |
seqlen = length(seq:) | |
for(seqlen: [i w~who ts~ts]{ | |
if ( not(equal(this.ts(i) this.w:)) ) { | |
this.w.same = false | |
} | |
}) | |
if ( who.same: ) { ret(who:) } | |
else { ret(0) } | |
} | |
forlist(tableSequences: [item cs~checkSequence w~winner] { | |
id = this.cs(item) | |
if ( gt(id: 0) ) { | |
this.w.id = id: | |
} | |
}) | |
ret(winner.id:) | |
} | |
runGameClick = [ | |
buttonID | |
mark = [buttonID] { | |
marked = markButton(buttonID playerTurn.current:) | |
if ( marked: ) { | |
playerTurn: | |
} | |
} | |
post = { | |
winner = findWinner: | |
if ( any(not(equal(winner: 0)) isGameBoardFull:) ) { | |
gameOver = true | |
displayWinnerBanner(winner) | |
} | |
} | |
] { | |
gameOver = isGameBoardFull: | |
if ( gameOver: ) { | |
ret: | |
} | |
r = this.mark(buttonID) | |
this.post: | |
} | |
runAI = [ | |
lookup = list: | |
pickFirstOpenSlot = { | |
i = 0 | |
tableLen = length(gameTable:) | |
loop { | |
if ( gte(i: tableLen:) ) { stop } | |
if ( equal(item_at(gameTable: i:) 0) ) { | |
runGameClick.mark(+(i: 1)) | |
stop | |
} | |
++(i:) | |
} | |
} | |
getTableStep = { | |
step = 0 | |
i = 0 | |
l = length(gameTable:) | |
loop { | |
if ( gte(i: l:) ) { stop } | |
if ( gt(item_at(gameTable: i:) 0) ) { | |
++(step:) | |
} | |
++(i:) | |
} | |
ret(step:) | |
} | |
takeCenter = { | |
if ( equal(item_at(gameTable: 4) 0) ) { | |
runGameClick.mark(5) | |
ret(true) | |
} | |
ret(false) | |
} | |
pickWinMove = { | |
# Find an empty slot on the end of or between two marked. A winning sequence has a value of 4. # | |
winseq.idx = 0 | |
winseq.found = false | |
forlist(tableSequences: [seq winseq~winseq]{ | |
total.value = 0 | |
this.winseq.valid = true | |
forlist(seq: [i total~total winseq~this.winseq]{ | |
gt_slot = item_at(gameTable: i:) | |
this.total.value = +(this.total.value: gt_slot:) | |
if ( equal(gt_slot: 1) ) { | |
this.winseq.valid = false | |
} | |
elif ( equal(gt_slot: 0) ) { | |
this.winseq.idx = i | |
} | |
}) | |
if ( all(equal(total.value: 4) this.winseq.valid:) ) { | |
this.winseq.found = true | |
this.winseq.savedIdx = this.winseq.idx | |
} | |
}) | |
if ( winseq.found: ) { | |
runGameClick.mark(+(winseq.savedIdx: 1)) | |
ret(true) | |
} | |
ret(false) | |
} | |
pickBlockingMove = { | |
pick.idx = 0 | |
pick.savedIdx = 0 | |
pick.found = false | |
forlist(tableSequences: [seq pick~pick]{ | |
occupancy.count = 0 | |
occupancy.total = 0 | |
forlist(seq: [i oc~occupancy pick~this.pick]{ | |
gt_slot = item_at(gameTable: i:) | |
this.oc.total = +(this.oc.total: gt_slot:) | |
if ( gt(gt_slot: 0) ) { | |
++(this.oc.count:) | |
} else { | |
this.pick.idx = i | |
} | |
}) | |
if ( equal(occupancy.total: occupancy.count: 2) ) { | |
this.pick.found = true | |
this.pick.savedIdx = this.pick.idx | |
} | |
}) | |
if ( pick.found: ) { | |
runGameClick.mark(+(pick.savedIdx: 1)) | |
ret(true) | |
} | |
ret(false) | |
} | |
# Pillar Move: | |
X | |
O | |
O | |
# | |
counterPillarMove = { | |
gtie = [idx value] { ret(equal(item_at(gameTable: idx:) value:)) } | |
step = super.getTableStep: | |
if ( equal(step: 3) ) { | |
if ( all(gtie(0 2) gtie(4 1) gtie(8 1)) ) { | |
runGameClick.mark(3) # upper right corner # | |
ret(true) | |
} | |
} | |
ret(false) | |
} | |
# LR Move: | |
_ _ _ | |
_ X O | |
_ O _ | |
# | |
counterLRMove = { | |
gtie = [idx value] { ret(equal(item_at(gameTable: idx:) value:)) } | |
step = super.getTableStep: | |
if ( equal(step: 3) ) { | |
if ( all(gtie(4 2) gtie(5 1) gtie(7 1)) ) { | |
runGameClick.mark(9) # lower right corner # | |
ret(true) | |
} | |
} | |
ret(false) | |
} | |
# Belt Move: | |
_ _ O | |
_ X _ | |
O _ _ | |
# | |
counterBeltMove = { | |
gtie = [idx value] { ret(equal(item_at(gameTable: idx:) value:)) } | |
step = super.getTableStep: | |
if ( equal(step: 3) ) { | |
if ( all(gtie(4 2) gtie(2 1) gtie(6 1)) ) { | |
runGameClick.mark(2) # top middle # | |
ret(true) | |
} | |
} | |
ret(false) | |
} | |
# Hockey Stick: | |
O _ _ | |
_ X O | |
_ _ _ | |
# | |
counterHockeyStickMove = { | |
gtie = [idx value] { ret(equal(item_at(gameTable: idx:) value:)) } | |
step = super.getTableStep: | |
if ( equal(step: 3) ) { | |
if ( gtie(4 2) ) { | |
if ( gtie(0 1) ) { | |
if ( gtie(5 1) ) { | |
runGameClick.mark(8) # bottom middle # | |
ret(true) | |
} | |
elif( gtie(7 1) ) { | |
runGameClick.mark(6) # right middle # | |
ret(true) | |
} | |
} | |
elif ( gtie(2 1) ) { | |
if ( gtie(7 1) ) { | |
runGameClick.mark(6) # right middle # | |
ret(true) | |
} | |
} | |
elif ( gtie(5 1) ) { | |
if ( gtie(6 1) ) { | |
runGameClick.mark(8) # bottom middle # | |
ret(true) | |
} | |
} | |
} | |
} | |
ret(false) | |
} | |
]{ | |
if ( equal(gameSettings.AIlevel: 1) ) { | |
this.pickFirstOpenSlot: | |
} | |
elif( equal(gameSettings.AIlevel: 2) ) { | |
if ( not(this.takeCenter:) ) { | |
if ( not(this.pickWinMove:) ) { | |
this.pickFirstOpenSlot: | |
} | |
} | |
} | |
elif( equal(gameSettings.AIlevel: 3) ) { | |
if (not(this.pickWinMove:)) { | |
if (not(this.pickBlockingMove:)) { | |
if ( not(this.takeCenter:) ) { | |
if ( not(this.counterPillarMove:) ) { | |
this.pickFirstOpenSlot: | |
} | |
} | |
} | |
} | |
} | |
elif ( equal(gameSettings.AIlevel: 4) ) { | |
if (not(this.pickWinMove:)) { | |
if (not(this.pickBlockingMove:)) { | |
if ( not(this.takeCenter:) ) { | |
if ( not(this.counterPillarMove:) ) { | |
if ( not(this.counterLRMove:) ) { | |
if ( not(this.counterBeltMove:) ) { | |
if ( not(this.counterHockeyStickMove:) ) { | |
this.pickFirstOpenSlot: | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
else { | |
this.pickFirstOpenSlot: | |
} | |
} | |
gui_on_button_clicked([elem_id]{ | |
if ( equal(elem_id: app.closeID:) ) { | |
close_application: | |
} | |
elif ( equal(elem_id: app.saveGameID:) ) { | |
save_cu(gameTable) | |
} | |
elif ( equal(elem_id: app.loadGameID:) ) { | |
load_cu(gameTable) | |
} | |
elif ( equal(elem_id: app.newGameID:) ) { | |
resetGame: | |
} | |
elif ( equal(elem_id: app.AIvsHumanID:) ) { | |
e = gui_child_with_id(mainMenuBar: app.AIvsHumanID:) | |
v = gui_specifics(e:) | |
gameSettings.AIenabled = not(gameSettings.AIenabled:) | |
if ( gameSettings.AIenabled: ) { | |
v.Image = getAIvsHumanImage: | |
} else { | |
v.Image = getHumanvsHumanImage: | |
} | |
gui_specifics(e: v) | |
resetGame: | |
} | |
elif ( all(gt(elem_id: 0) gte(9 elem_id:)) ) { | |
if ( not(gameOver:) ) { | |
runGameClick(elem_id) | |
if ( all(not(gameOver:) gameSettings.AIenabled: equal(playerTurn.current: 2)) ) { | |
runAI: | |
runGameClick.post: | |
} | |
} | |
} | |
}) | |
gui_on_combobox_changed([elem_id]{ | |
if ( equal(elem_id: app.AILevelID:) ) { | |
e = gui_child_with_id(mainMenuBar: app.AILevelID:) | |
v = gui_value(e:) | |
gameSettings.AIlevel = +(1 v.Selected:) | |
resetGame: | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment