Aloittelijalle miinaharavan toteutus on hyvä ja kohtuullisen palkitseva harjoitus.
Ohessa rakenteeltaan äärimmäisen yksinkertainen, mutta täysi miinaharava pelin toteutus kirjoitettuna Hollywoodilla. Oleelliset kohdat koodista kääntyvät helposti mille tahansa ohjelmointikielelle, joten pelin perusrunko on valmiina käytettäväksi omiin projekteihin.
lähdekoodit ja binäärit täältä
Piirtorutiinien toteutuksen olen mallintanut Infernolta, koska olen niihin jotenkin kiintynyt... ;-)
;
; Hollywood Mine Sweeper v. 0.2
;
; Jali Heinonen
; jali.heinonen@gmail.com
;
@APPICON { Ic16x16 = "icons/my16x16icon.png",
Ic24x24 = "icons/my24x24icon.png",
Ic32x32 = "icons/my32x32icon.png",
Ic48x48 = "icons/my48x48icon.png",
Ic128x128 = "icons/my128x128icon.png",
Ic256x256 = "icons/my256x256icon.png",
Ic512x512 = "icons/my512x512icon.png" }
@DISPLAY { Title = "Hollywood Mine Sweeper v. 0.2", Width = 384, Height = 384+32, Hidden = True }
; Include game framework
@INCLUDE "hgf/game.hws"
@BRUSH 1, "data/tile.png", {LoadAlpha = True}
@BRUSH 2, "data/button_down.png", {LoadAlpha = True}
@BRUSH 3, "data/mine.png", {LoadAlpha = True}
@BRUSH 4, "data/button_up.png", {LoadAlpha = True}
@BRUSH 8, "data/explosion.png", {LoadAlpha = True}
@BRUSH 9, "data/flag.png", {LoadAlpha = True}
@BRUSH 10, "data/n0.png", {LoadAlpha = True}
@BRUSH 11, "data/n1.png", {LoadAlpha = True}
@BRUSH 12, "data/n2.png", {LoadAlpha = True}
@BRUSH 13, "data/n3.png", {LoadAlpha = True}
@BRUSH 14, "data/n4.png", {LoadAlpha = True}
@BRUSH 15, "data/n5.png", {LoadAlpha = True}
@BRUSH 16, "data/n6.png", {LoadAlpha = True}
@BRUSH 17, "data/n7.png", {LoadAlpha = True}
@BRUSH 18, "data/n8.png", {LoadAlpha = True}
; Board size, internally board is 2 tiles larger
Const #BRDSZ = 12
Const #BRDSZI = #BRDSZ + 2
; Board tile states
Const #UNSELECTED = 1
Const #SELECTED = 2
Const #MARKED = 4
; Skill level
Const #EASY = 25
Function init_board()
Local i, j
Local row, column
finished = False
score = 0
mines = 0
; initialize board
board = {}
For i = 0 To #BRDSZI - 1
board[i] = {}
For j = 0 To #BRDSZI - 1
board[i][j] = {}
board[i][j].mine = 0
board[i][j].state = #UNSELECTED
Next
Next
;place mines
For i = 0 To #EASY - 1
j = Rnd(#BRDSZ*#BRDSZ)
row = Int((j/#BRDSZ)+1)
column = Int((j%#BRDSZ)+1)
If board[row][column].mine = 0
board[row][column].mine = 1
mines = mines + 1
EndIf
Next
EndFunction
Function display_square(i, j, img)
game.buffer:drawImg(img.r:addpt({ x = (j-1) * img.r:dx(), y = i * img.r:dy() }), img, Point:new())
EndFunction
Function display_mines()
For Local i = 1 To #BRDSZ
For Local j = 1 To #BRDSZ
If board[i][j].mine = 1 Then display_square(i, j, img3)
Next
Next
EndFunction
Function display_score()
Local blankr = Rect:new({ min = { x = 140, y = 2}, max = { x = 384, y = 30 } })
Local score_text$ = "score: " ..PadNum(score, 3)
Local percentage$ = PadNum(Int(score/((#BRDSZ*#BRDSZ)-mines)*100), 3) .."%"
SetFont(#MONOSPACE, 20)
SetFontStyle(#BOLD|#ANTIALIAS)
SetFontColor(#BLACK)
SetFillStyle(#FILLCOLOR)
game.buffer:drawRect(blankr, $CCCCCC)
game.buffer:drawText({ x = 150, y = 6 }, percentage$)
game.buffer:drawText({ x = 250, y = 6 }, score_text$)
EndFunction
Function board_check(i, j)
If board[i][j].mine = 1 Then Return(-1)
If board[i][j].state & (#SELECTED|#MARKED) Then Return(-2)
Local c = 0
For Local row = i-1 To i+1
For Local col = j-1 To j+1
If board[row][col].mine = 1 Then c = c + 1
Next
Next
Return(c)
EndFunction
Function mark_square(i, j)
Switch board[i][j].state
Case #UNSELECTED:
board[i][j].state = #MARKED
display_square(i, j, img7)
Case #MARKED
board[i][j].state = #UNSELECTED
display_square(i, j, img6)
EndSwitch
EndFunction
Function display_zeros(i, j)
For Local row = i-1 To i+1
For Local col = j-1 To j+1
If row < 1 Or row > #BRDSZ Or col < 1 Or col > #BRDSZ Then Continue
Local tile = board_check(row, col)
If tile = 0
score = score + 1
board[row][col].state = #SELECTED
display_square(row, col, images[tile])
display_zeros(row, col)
Else
If tile >= 1 And tile <= 8
board[row][col].state = #SELECTED
display_square(row, col, images[tile])
score = score + 1
EndIf
EndIf
Next
Next
EndFunction
Function init_gui()
MakeButton(1000, #SIMPLEBUTTON, 10, 2, img4.r:dx(), img4.r:dy(), { OnMouseDown = menu_handler, OnMouseUp = menu_handler, OnMouseOver = menu_handler, OnMouseOut = menu_handler })
Local num_button = 1
Local tiler = Rect:new({min = { x = 0, y = 0 }, max = { x = 32, y = 32 }})
Local pos = Point:new({ x = 0, y = 32 })
; Create buttons
For Local i = 1 To #BRDSZ
For Local j = 1 To #BRDSZ
MakeButton(num_button, #SIMPLEBUTTON, tiler:addpt(pos).min.x, tiler:addpt(pos).min.y, tiler:addpt(pos):dx(), tiler:addpt(pos):dy(), { OnMouseUp = board_handler, OnRightMouseUp = board_handler, OnMouseOver = board_handler, OnMouseOut = board_handler })
pos.x = pos.x + tiler:dx()
num_button = num_button + 1
Next
pos.x = 0
pos.y = pos.y + tiler:dy()
Next
EndFunction
Function draw_board()
Local panelr = Rect:new({ min = { x = 0, y = 0 }, max = { x = 384, y = 32 } })
SetFillStyle(#FILLCOLOR)
game.buffer:drawRect(panelr, $CCCCCC)
game.buffer:drawImg(img4.r:addpt({ x = 10, y = 2 }), img4, Point:new())
display_score()
Local pos = Point:new({ x = 0, y = 32 })
; Draw button tiles
For Local i = 1 To #BRDSZ
For Local j = 1 To #BRDSZ
game.buffer:drawImg(img1.r:addpt(pos), img1, Point:new())
pos.x = pos.x + img1.r:dx()
Next
pos.x = 0
pos.y = pos.y + img1.r:dy()
Next
EndFunction
Function menu_handler(msg)
Switch msg.action
Case "OnMouseDown":
game.buffer:drawImg(img2.r:addpt({ x = 10, y = 2 }), img2, Point:new())
Case "OnMouseUp":
finished = False
map = init_board()
draw_board()
game.buffer:drawImg(img10.r:addpt({ x = 10, y = 2 }), img10, Point:new())
Case "OnMouseOver":
game.buffer:drawImg(img10.r:addpt({ x = 10, y = 2 }), img10, Point:new())
Case "OnMouseOut":
game.buffer:drawImg(img4.r:addpt({ x = 10, y = 2 }), img4, Point:new())
EndSwitch
EndFunction
Function board_handler(msg)
If finished Then Return
Local by = Int(msg.id / #BRDSZ)
Local bx = msg.id % #BRDSZ
If bx = 0 Then bx = #BRDSZ Else by = by + 1
Switch msg.action
Case "OnMouseUp":
; Handle game input
Local i = board_check(by,bx)
If i = -1 And board[by][bx].state <> #MARKED
display_mines()
display_square(by, bx, img8)
finished = True
ElseIf i >= 0 And i <= 8
score = score + 1
board[by][bx].state = #SELECTED
display_square(by, bx, images[i])
If i = 0 Then display_zeros(by, bx)
display_score()
If score + mines = #BRDSZ*#BRDSZ
display_mines()
finished = True
Endif
EndIf
Case "OnRightMouseUp"
mark_square(by, bx)
Case "OnMouseOver":
If board[by][bx].state = #SELECTED Then Break
If board[by][bx].state = #MARKED Then display_square(by, bx, img7) Else display_square(by, bx, img6)
Case "OnMouseOut":
If board[by][bx].state = #SELECTED Then Break
If board[by][bx].state = #MARKED Then display_square(by, bx, img9) Else display_square(by, bx, img1)
EndSwitch
EndFunction
; Create tinted images for selection highlighting
CopyBrush(1, 6)
TintBrush(6, #WHITE, 128)
CopyBrush(9, 7)
TintBrush(7, #WHITE, 128)
; Create tinted image for menu GUI button highlighting
CopyBrush(4, 19)
TintBrush(19, #WHITE, 128)
; Create game images
img1 = Image:new(1)
img2 = Image:new(2)
img3 = Image:new(3)
img4 = Image:new(4)
img6 = Image:new(6)
img7 = Image:new(7)
img8 = Image:new(8)
img9 = Image:new(9)
img10 = Image:new(19)
images = {}
images[0] = Image:new(10)
images[1] = Image:new(11)
images[2] = Image:new(12)
images[3] = Image:new(13)
images[4] = Image:new(14)
images[5] = Image:new(15)
images[6] = Image:new(16)
images[7] = Image:new(17)
images[8] = Image:new(18)
; Init game, draw the board and GUI
init_gui()
init_board()
game.buffer:init()
draw_board()
OpenDisplay(1)
; Process events
Repeat
WaitEvent
ForeverJos oletetaan tämän koodivinkin kohdeyleisönä olevan aloittelijat, jotka eivät lähtökohtaisesti osaa itse miinaharavaa toteuttaa, niin se on melko huono. Jos on tarkoituksena opettaa, niin täytyisi kertoa _miksi_ asioita tehdään niin kuin tehdään. Kommentointi ei tuossa ole sen mukaista.
Loppupuolella kaivattaisiin myös lisää silmukkoja ja esim. init board funktiossa alustus ja miinojen asettelu olisi helposti yhdistettävissä lyhyemmäksi koodiksi. En tätä testannut, mutta se kokonaisuutena näyttää siltä, että sait sen juuri kasaan ja pullautit sitten suoraan tänne näkyville. Ei siinä mitään pahaa sinänsä, on aina hienoa saada jotain aikaan, mutta opetusmateriaaliksi tästä ei mielestäni ole ilman monen osan uudelleenkirjoitusta tai -jäsentelyä.
mietinvaan kirjoitti:
Loppupuolella kaivattaisiin myös lisää silmukkoja ja esim. init board funktiossa alustus ja miinojen asettelu olisi helposti yhdistettävissä lyhyemmäksi koodiksi.
Mitähän höpiset? Tuossa init_board() funktiossa nollataan ensin pelilaudan tila ja arvotaan sitten sopiva määrä miinoja pelialueelle. Mitähän meinasit tuossa yhdistellä lyhyemmäksi koodiksi?
Jos tietää miten Hollywood-ohjelma prosessoi tapahtumia, niin ohjelman kulkua on kuitenkin aika helppo seurata. En hirveästi näe järkeä kommentoida jokaista riviä lyhkäisissä funktioissa, joiden nimet sinällään jo kertovat mitä tehdään.
Minusta tämä on muuten kohtuullisen selvä vinkki, mutta BRDSZ(I) on epäselvä nimi ja myös kuvien nimet kannattaisi vaihtaa järkeviksi eli kuvan sisältöä vastaaviksi.
Miinaharavan PL/I:llä kirjoiteltu Windows versio löytyy täältä.
Aihe on jo aika vanha, joten et voi enää vastata siihen.